@dynamicagents/plugins 0.14.0 → 0.15.0
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/README.md +14 -14
- package/dist/claude-code/cards.d.ts +2 -0
- package/dist/claude-code/cards.js +11 -0
- package/dist/claude-code/config.d.ts +10 -83
- package/dist/claude-code/config.js +0 -2
- package/dist/claude-code/credentials.d.ts +32 -50
- package/dist/claude-code/credentials.js +115 -74
- package/dist/claude-code/daemon/index.d.ts +24 -0
- package/dist/claude-code/daemon/index.js +203 -0
- package/dist/claude-code/egress.d.ts +35 -117
- package/dist/claude-code/egress.js +80 -325
- package/dist/claude-code/events.d.ts +14 -17
- package/dist/claude-code/events.js +19 -15
- package/dist/claude-code/harness-wire.d.ts +45 -0
- package/dist/claude-code/harness-wire.js +47 -0
- package/dist/claude-code/index.d.ts +19 -29
- package/dist/claude-code/index.js +17 -25
- package/dist/claude-code/language-model.d.ts +19 -0
- package/dist/claude-code/language-model.js +71 -0
- package/dist/claude-code/model.d.ts +77 -70
- package/dist/claude-code/model.js +327 -327
- package/dist/claude-code/placeholder.d.ts +12 -0
- package/dist/claude-code/placeholder.js +12 -0
- package/dist/claude-code/spec.d.ts +4 -4
- package/dist/claude-code/spec.js +4 -4
- package/dist/computer/client.d.ts +88 -0
- package/dist/computer/client.js +6 -0
- package/dist/computer/context.d.ts +7 -7
- package/dist/computer/context.js +14 -18
- package/dist/computer/gate.d.ts +4 -23
- package/dist/computer/gate.js +1 -37
- package/dist/computer/index.d.ts +44 -43
- package/dist/computer/index.js +29 -50
- package/dist/computer/paths.d.ts +5 -27
- package/dist/computer/paths.js +7 -30
- package/dist/computer/proxy.d.ts +17 -31
- package/dist/computer/proxy.js +9 -44
- package/dist/computer/render.d.ts +3 -40
- package/dist/computer/render.js +3 -36
- package/dist/computer/sandbox.d.ts +55 -0
- package/dist/computer/sandbox.js +236 -0
- package/dist/computer/shell.d.ts +36 -0
- package/dist/computer/shell.js +41 -0
- package/dist/computer/tools-exec.js +3 -9
- package/dist/computer/tools-file.d.ts +1 -1
- package/dist/computer/tools-file.js +1 -1
- package/dist/{repo → repo-github}/checkout.d.ts +3 -4
- package/dist/{repo → repo-github}/checkout.js +3 -4
- package/dist/{repo → repo-github}/context.d.ts +14 -32
- package/dist/{repo → repo-github}/context.js +46 -81
- package/dist/{repo → repo-github}/index.d.ts +30 -13
- package/dist/{repo → repo-github}/index.js +25 -6
- package/dist/repo-github/mcp.d.ts +95 -0
- package/dist/repo-github/mcp.js +107 -0
- package/dist/repo-github/sandbox-egress.d.ts +68 -0
- package/dist/repo-github/sandbox-egress.js +272 -0
- package/dist/repo-github/sandbox-git.d.ts +30 -0
- package/dist/repo-github/sandbox-git.js +113 -0
- package/dist/{repo → repo-github}/tools-checks.js +43 -4
- package/dist/{repo → repo-github}/tools-clone.js +2 -21
- package/dist/{repo → repo-github}/tools-pr.d.ts +0 -1
- package/dist/{repo → repo-github}/tools-pr.js +85 -20
- package/dist/{repo → repo-github}/tools-review.d.ts +6 -0
- package/dist/{repo → repo-github}/tools-review.js +62 -176
- package/dist/{repo → repo-github}/url.d.ts +11 -0
- package/dist/{repo → repo-github}/url.js +12 -1
- package/dist/{workspace → sandbox}/advisory.d.ts +16 -35
- package/dist/{workspace → sandbox}/advisory.js +2 -29
- package/dist/sandbox/agent-exec.d.ts +31 -0
- package/dist/sandbox/agent-exec.js +62 -0
- package/dist/sandbox/ca-trust.d.ts +9 -0
- package/dist/sandbox/ca-trust.js +21 -0
- package/dist/sandbox/constants.d.ts +22 -0
- package/dist/sandbox/constants.js +22 -0
- package/dist/sandbox/daemon/composite.d.ts +9 -0
- package/dist/sandbox/daemon/composite.js +35 -0
- package/dist/sandbox/daemon/index.d.ts +47 -0
- package/dist/sandbox/daemon/index.js +116 -0
- package/dist/sandbox/egress.d.ts +81 -0
- package/dist/sandbox/egress.js +109 -0
- package/dist/sandbox/exec.d.ts +57 -0
- package/dist/sandbox/exec.js +52 -0
- package/dist/sandbox/format.d.ts +2 -0
- package/dist/sandbox/format.js +7 -0
- package/dist/sandbox/header.d.ts +50 -0
- package/dist/sandbox/header.js +94 -0
- package/dist/sandbox/index.d.ts +17 -0
- package/dist/sandbox/index.js +17 -0
- package/dist/{workspace/install.d.ts → sandbox/install-plan.d.ts} +2 -2
- package/dist/{workspace/install.js → sandbox/install-plan.js} +2 -2
- package/dist/sandbox/name.d.ts +27 -0
- package/dist/sandbox/name.js +32 -0
- package/dist/sandbox/object.d.ts +244 -0
- package/dist/sandbox/object.js +1139 -0
- package/dist/scratch/index.d.ts +1 -9
- package/dist/scratch/index.js +2 -18
- package/package.json +22 -18
- package/dist/claude-code/github.d.ts +0 -39
- package/dist/claude-code/github.js +0 -82
- package/dist/claude-code/run.d.ts +0 -491
- package/dist/claude-code/run.js +0 -913
- package/dist/claude-code/session.d.ts +0 -131
- package/dist/claude-code/session.js +0 -288
- package/dist/workspace/ca-trust.d.ts +0 -95
- package/dist/workspace/ca-trust.js +0 -312
- package/dist/workspace/container-deps.d.ts +0 -58
- package/dist/workspace/container-deps.js +0 -249
- package/dist/workspace/container-fault.d.ts +0 -38
- package/dist/workspace/container-fault.js +0 -73
- package/dist/workspace/deps-snapshot.d.ts +0 -110
- package/dist/workspace/deps-snapshot.js +0 -424
- package/dist/workspace/exec.d.ts +0 -94
- package/dist/workspace/exec.js +0 -153
- package/dist/workspace/format.d.ts +0 -36
- package/dist/workspace/format.js +0 -57
- package/dist/workspace/git-host.d.ts +0 -79
- package/dist/workspace/git-host.js +0 -226
- package/dist/workspace/git-identity.d.ts +0 -57
- package/dist/workspace/git-identity.js +0 -147
- package/dist/workspace/index.d.ts +0 -50
- package/dist/workspace/index.js +0 -58
- package/dist/workspace/install-job.d.ts +0 -170
- package/dist/workspace/install-job.js +0 -934
- package/dist/workspace/object.d.ts +0 -522
- package/dist/workspace/object.js +0 -1541
- package/dist/workspace/open.d.ts +0 -95
- package/dist/workspace/open.js +0 -63
- package/dist/workspace/read.d.ts +0 -16
- package/dist/workspace/read.js +0 -22
- package/dist/workspace/shell.d.ts +0 -45
- package/dist/workspace/shell.js +0 -80
- package/dist/workspace/sync.d.ts +0 -111
- package/dist/workspace/sync.js +0 -124
- package/dist/workspace/wake.d.ts +0 -21
- package/dist/workspace/wake.js +0 -10
- /package/dist/{repo → repo-github}/tools-checks.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-clone.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.js +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.js +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { parseStream, toProgress } from "../events.js";
|
|
2
|
+
import { ANTHROPIC_PLACEHOLDER_HOST, encodeNote, encodeRunEnd, encodeSessionId } from "../harness-wire.js";
|
|
3
|
+
import { CREDENTIAL_PLACEHOLDER } from "../placeholder.js";
|
|
4
|
+
/** What the end of stderr is kept to, when a process leaves with no result. */
|
|
5
|
+
const STDERR_KEPT = 2_000;
|
|
6
|
+
/** Claude Code, as `serveSandboxDaemon`'s `cli`. */
|
|
7
|
+
export function claudeCodeDaemonCli(options) {
|
|
8
|
+
// Moved, the conversations land outside `.claude/projects`, which is all the
|
|
9
|
+
// harness mirrors, and a lost container could not resume one.
|
|
10
|
+
if (options.env && "CLAUDE_CONFIG_DIR" in options.env) {
|
|
11
|
+
throw new Error("claude-code: CLAUDE_CONFIG_DIR cannot be set through `env`: each session " +
|
|
12
|
+
"keeps its conversations under its own home's .claude/projects, which " +
|
|
13
|
+
"is what the harness keeps for a lost container to resume from");
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
id: options.id,
|
|
17
|
+
// Where the CLI keeps its conversations, under the session's home: the
|
|
18
|
+
// daemon mirrors them to the object, so a lost container resumes them.
|
|
19
|
+
stateDirs: [".claude/projects"],
|
|
20
|
+
command: (turn) => command(options, turn),
|
|
21
|
+
parser: (turn) => parser(turn.operationId)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One turn's command.
|
|
26
|
+
*
|
|
27
|
+
* `stream-json` with `--verbose`, because the stream is the only way to report
|
|
28
|
+
* progress before a run of minutes ends, and without `--verbose` the CLI emits
|
|
29
|
+
* only the final result even in stream mode. The mode is always passed, never
|
|
30
|
+
* left to the CLI's own — see `../config.ts` `ClaudeCodeConfig.permissionMode`.
|
|
31
|
+
*
|
|
32
|
+
* A resumed turn's prompt is the next user turn. An id whose conversation is
|
|
33
|
+
* not there is not a failed session: the CLI emits a `result` naming it, with
|
|
34
|
+
* no model call and nothing billed. A fork continues the conversation under a
|
|
35
|
+
* new id, which is what a change of mode wants — a plan's conversation stays
|
|
36
|
+
* whole for an edit afterwards.
|
|
37
|
+
*
|
|
38
|
+
* What is absent: no `--bare` and no `--settings` override. A cloned
|
|
39
|
+
* repository's `CLAUDE.md`, skills and hooks are what make the agent good at
|
|
40
|
+
* it, and the container already runs that repository's `postinstall` and test
|
|
41
|
+
* suite by design.
|
|
42
|
+
*/
|
|
43
|
+
function command(options, turn) {
|
|
44
|
+
const header = turn.header;
|
|
45
|
+
const mode = header?.mode === "plan"
|
|
46
|
+
? "plan"
|
|
47
|
+
: (options.permissionMode ?? "bypassPermissions");
|
|
48
|
+
const argv = [
|
|
49
|
+
"claude",
|
|
50
|
+
"-p",
|
|
51
|
+
"--output-format",
|
|
52
|
+
"stream-json",
|
|
53
|
+
"--verbose",
|
|
54
|
+
"--permission-mode",
|
|
55
|
+
mode
|
|
56
|
+
];
|
|
57
|
+
const sessionId = turn.state.sessionId;
|
|
58
|
+
if (sessionId) {
|
|
59
|
+
argv.push("--resume", sessionId);
|
|
60
|
+
if (header?.fork)
|
|
61
|
+
argv.push("--fork-session");
|
|
62
|
+
}
|
|
63
|
+
const model = header?.model ?? turn.settings.model ?? options.model;
|
|
64
|
+
if (model)
|
|
65
|
+
argv.push("--model", model);
|
|
66
|
+
const effort = header?.effort ?? options.effort;
|
|
67
|
+
if (effort)
|
|
68
|
+
argv.push("--effort", effort);
|
|
69
|
+
if (header?.schema !== undefined) {
|
|
70
|
+
const schema = options.schemas[header.schema];
|
|
71
|
+
if (!schema)
|
|
72
|
+
throw new Error(`no schema named ${header.schema} in this image`);
|
|
73
|
+
argv.push("--json-schema", JSON.stringify(schema));
|
|
74
|
+
}
|
|
75
|
+
return { argv, stdin: turn.prompt, env: sessionEnv(options) };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The session's environment. No autoupdate: the image is pinned, and an update
|
|
79
|
+
* would move the wire shape the egress and the parser are written against,
|
|
80
|
+
* mid-run. No nonessential traffic: telemetry and feature-flag fetches, off at
|
|
81
|
+
* the source. The CLI runs as the agent rather than root, so no `IS_SANDBOX`,
|
|
82
|
+
* and each session has its own home, so no `CLAUDE_CONFIG_DIR`.
|
|
83
|
+
*
|
|
84
|
+
* Git's identity is an environment, not a `git config`: every git the session
|
|
85
|
+
* starts inherits it — a superproject's submodules, a scratch clone — and it
|
|
86
|
+
* outranks every config level. Both pairs, because an amend or a rebase keeps
|
|
87
|
+
* the author and stamps a fresh committer. Applied after `env`, so the four
|
|
88
|
+
* keys are one answer.
|
|
89
|
+
*
|
|
90
|
+
* The credential and its base URL are applied last, so a deployment's `env`
|
|
91
|
+
* cannot point the CLI at a real one. What other hosts need — git's and `gh`'s
|
|
92
|
+
* placeholders — the CLI inherits from the daemon, whose environment the
|
|
93
|
+
* sandbox fills from its egress rules.
|
|
94
|
+
*/
|
|
95
|
+
function sessionEnv(options) {
|
|
96
|
+
const author = options.author;
|
|
97
|
+
return {
|
|
98
|
+
DISABLE_AUTOUPDATER: "1",
|
|
99
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
100
|
+
...(options.maxSubagentDepth === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: {
|
|
103
|
+
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: String(options.maxSubagentDepth)
|
|
104
|
+
}),
|
|
105
|
+
...(options.maxConcurrentSubagents === undefined
|
|
106
|
+
? {}
|
|
107
|
+
: {
|
|
108
|
+
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: String(options.maxConcurrentSubagents)
|
|
109
|
+
}),
|
|
110
|
+
...options.env,
|
|
111
|
+
...(author
|
|
112
|
+
? {
|
|
113
|
+
GIT_AUTHOR_NAME: author.name,
|
|
114
|
+
GIT_AUTHOR_EMAIL: author.email,
|
|
115
|
+
GIT_COMMITTER_NAME: author.name,
|
|
116
|
+
GIT_COMMITTER_EMAIL: author.email
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
119
|
+
ANTHROPIC_BASE_URL: `http://${ANTHROPIC_PLACEHOLDER_HOST}`,
|
|
120
|
+
CLAUDE_CODE_OAUTH_TOKEN: CREDENTIAL_PLACEHOLDER
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parser(operationId) {
|
|
124
|
+
let thinkingFrom;
|
|
125
|
+
/** Notes so far, which positions each note's key. */
|
|
126
|
+
let n = 0;
|
|
127
|
+
let result;
|
|
128
|
+
let rateLimit;
|
|
129
|
+
let sessionId;
|
|
130
|
+
return {
|
|
131
|
+
line(line) {
|
|
132
|
+
const at = Date.now();
|
|
133
|
+
const parsed = parseStream(`${line}\n`, {
|
|
134
|
+
now: at,
|
|
135
|
+
...(thinkingFrom === undefined ? {} : { thinkingFrom })
|
|
136
|
+
});
|
|
137
|
+
thinkingFrom = parsed.thinkingFrom;
|
|
138
|
+
// The resume state the harness hands the next turn of this session.
|
|
139
|
+
let state;
|
|
140
|
+
for (const event of parsed.events) {
|
|
141
|
+
if (event.kind === "init") {
|
|
142
|
+
sessionId = event.sessionId;
|
|
143
|
+
state = { sessionId };
|
|
144
|
+
}
|
|
145
|
+
else if (event.kind === "rateLimit")
|
|
146
|
+
rateLimit = event.info;
|
|
147
|
+
else if (event.kind === "result")
|
|
148
|
+
result = event.result;
|
|
149
|
+
}
|
|
150
|
+
const message = (id, text) => ({
|
|
151
|
+
type: "message",
|
|
152
|
+
message: {
|
|
153
|
+
id: `${operationId}:${id}`,
|
|
154
|
+
role: "assistant",
|
|
155
|
+
parts: [{ type: "text", text }],
|
|
156
|
+
operationId,
|
|
157
|
+
createdAt: at
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const events = [];
|
|
161
|
+
if (state)
|
|
162
|
+
events.push(message("session", encodeSessionId(state.sessionId)));
|
|
163
|
+
const notes = toProgress(parsed.events, n);
|
|
164
|
+
n += notes.length;
|
|
165
|
+
for (const note of notes)
|
|
166
|
+
events.push(message(note.key, encodeNote(note)));
|
|
167
|
+
if (parsed.skipped > 0) {
|
|
168
|
+
console.warn("[claude-code] a stream line was not recognised", {
|
|
169
|
+
operationId,
|
|
170
|
+
sample: parsed.sample
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { events, ...(state ? { state } : {}) };
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* The outcome, once the process has exited — the harness settles nothing
|
|
177
|
+
* before that either. A result with the code the CLI exited with, since a
|
|
178
|
+
* result can be an error it exits non-zero on; with no result, its stderr.
|
|
179
|
+
*/
|
|
180
|
+
end(exit) {
|
|
181
|
+
if (result) {
|
|
182
|
+
return {
|
|
183
|
+
status: "done",
|
|
184
|
+
text: encodeRunEnd({
|
|
185
|
+
result,
|
|
186
|
+
exitCode: exit.code,
|
|
187
|
+
...(rateLimit ? { rateLimit } : {}),
|
|
188
|
+
...(sessionId ? { sessionId } : {})
|
|
189
|
+
})
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
status: "unanswered",
|
|
194
|
+
reason: encodeRunEnd({
|
|
195
|
+
exitCode: exit.code,
|
|
196
|
+
stderr: exit.stderr.slice(-STDERR_KEPT),
|
|
197
|
+
...(rateLimit ? { rateLimit } : {}),
|
|
198
|
+
...(sessionId ? { sessionId } : {})
|
|
199
|
+
})
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -1,126 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { EgressCall, SandboxEgressRule } from "../sandbox/egress.js";
|
|
2
|
+
import { type Lead } from "./credentials.js";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* interception CA" section is the whole of what that takes.
|
|
17
|
-
*
|
|
18
|
-
* That position is what makes three otherwise-hard things easy, and they are the
|
|
19
|
-
* reason this file exists rather than a plain allowlist:
|
|
20
|
-
*
|
|
21
|
-
* 1. **The real credential never enters the container.** Claude Code is launched
|
|
22
|
-
* with a placeholder; the swap happens here. A `postinstall` script in a
|
|
23
|
-
* cloned repository can read every environment variable the container has and
|
|
24
|
-
* still learn nothing — which is the repository's standing rule ("hand it the
|
|
25
|
-
* action, not the credential") applied to a process nobody can constrain. A
|
|
26
|
-
* GitHub token, when configured, is held the same way — see `./github.ts`.
|
|
27
|
-
* 2. **Any restriction that is applied is total.** Not a policy the container
|
|
28
|
-
* cooperates with; the only route out. Restriction is **off by default** —
|
|
29
|
-
* see {@link EgressConfig.restrictToHosts} for why.
|
|
30
|
-
* 3. **Exhaustion is seen, so it can be routed around.** This is the only place
|
|
31
|
-
* Anthropic's actual response is visible, and therefore the only place that
|
|
32
|
-
* can learn a subscription bucket is empty. See {@link EgressConfig.store}
|
|
33
|
-
* and `credentials.ts`.
|
|
34
|
-
*
|
|
35
|
-
* ## What this deliberately does not do
|
|
36
|
-
*
|
|
37
|
-
* It does not meter, and a budget gate must not come back: an estimate of spend
|
|
38
|
-
* is a guess about a bucket nobody can read, moves only once per run, and the
|
|
39
|
-
* bucket announces the answer exactly — see the docblock in `credentials.ts`.
|
|
40
|
-
*
|
|
41
|
-
* And it does not, by default, **bound exfiltration**. The container holds the
|
|
42
|
-
* checkout, and with no restriction configured it can send it anywhere. That is
|
|
43
|
-
* a deliberate default rather than an oversight — the reasoning is on
|
|
44
|
-
* {@link EgressConfig.restrictToHosts} — and what it means for a reader is that
|
|
45
|
-
* the containment here is "the container holds no credential", full stop. Do not
|
|
46
|
-
* read a host restriction that is switched off as a boundary that exists.
|
|
4
|
+
* The Anthropic credential, swapped in on the Worker side of a sandbox.
|
|
5
|
+
*
|
|
6
|
+
* The CLI holds a placeholder OAuth token, this sets the pool's lead as the
|
|
7
|
+
* Bearer, and a refusal that spends or rejects the credential moves the lead
|
|
8
|
+
* and tells the CLI to retry on the next one — `./credentials.ts` reads the
|
|
9
|
+
* refusal. A sandbox's egress entrypoint is stateless, so the pool lives in the
|
|
10
|
+
* sandbox the request came from, reached through `call.sandbox()` as a
|
|
11
|
+
* {@link CredentialLease}.
|
|
12
|
+
*
|
|
13
|
+
* The CLI reaches this over plain HTTP to a placeholder host the harness
|
|
14
|
+
* intercepts, so nothing in the container terminates TLS. The daemon points
|
|
15
|
+
* the CLI at it (`./daemon/index.ts`), after the deployment's own environment,
|
|
16
|
+
* so the rule sets no `env`.
|
|
47
17
|
*/
|
|
48
18
|
/** Anthropic's API host — the one destination that gets a credential. */
|
|
49
19
|
export declare const ANTHROPIC_HOST = "api.anthropic.com";
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Where the pool's `{ index → resetAt }` map is persisted.
|
|
61
|
-
*
|
|
62
|
-
* Supplied by the host because the state belongs to the host's storage: it is
|
|
63
|
-
* kept per workspace Durable Object, so each workspace learns a rotation
|
|
64
|
-
* independently. The cost of that is one wasted `429` per workspace per
|
|
65
|
-
* rotation; the saving is a Durable Object class, a binding and a migration.
|
|
66
|
-
*/
|
|
67
|
-
store: CredentialStore;
|
|
68
|
-
/**
|
|
69
|
-
* Restrict the container to these hosts, plus `api.anthropic.com`.
|
|
70
|
-
*
|
|
71
|
-
* **Three-way, and each value means literally what it says:**
|
|
72
|
-
*
|
|
73
|
-
* | Value | Effect |
|
|
74
|
-
* |---|---|
|
|
75
|
-
* | omitted | unrestricted — the default |
|
|
76
|
-
* | `["registry.npmjs.org"]` | that host, plus Anthropic |
|
|
77
|
-
* | `[]` | Anthropic only |
|
|
78
|
-
*
|
|
79
|
-
* An empty array is *not* the same as omitting the field, deliberately. A host
|
|
80
|
-
* computing this list — `repos.flatMap(hostsFor)`, say — that happens to
|
|
81
|
-
* produce `[]` means "nothing extra", and collapsing that into "everything"
|
|
82
|
-
* would hand the widest possible policy to an expression that returned
|
|
83
|
-
* nothing.
|
|
84
|
-
*
|
|
85
|
-
* ## Why unrestricted is the default
|
|
86
|
-
*
|
|
87
|
-
* A curated list is permanently wrong for a coding agent. `esbuild`, `swc` and
|
|
88
|
-
* `sharp` fetch prebuilt binaries from release CDNs; Playwright downloads
|
|
89
|
-
* browsers from a third host; corepack fetches package managers; and reading
|
|
90
|
-
* documentation is part of the job. The failure mode is the bad one — `npm ci`
|
|
91
|
-
* dying inside a `postinstall` with a network error nobody connects to a list
|
|
92
|
-
* three files away.
|
|
93
|
-
*
|
|
94
|
-
* Little is lost by defaulting open, because **the restriction was never what
|
|
95
|
-
* protected the credential**. The swap is keyed on the destination being
|
|
96
|
-
* Anthropic and credential headers are stripped from everything else, so both
|
|
97
|
-
* hold whatever this says. What an unrestricted policy does give up is a bound
|
|
98
|
-
* on *exfiltration*: the container holds the checkout and can send it
|
|
99
|
-
* anywhere. That matches `/computer`, whose egress has always been
|
|
100
|
-
* unrestricted, for the same reason.
|
|
101
|
-
*
|
|
102
|
-
* Matched **exactly** on hostname when set. No wildcards, and that is not an
|
|
103
|
-
* omission: `*.example.com` is how a restriction quietly becomes no
|
|
104
|
-
* restriction, the first time a host serves user-controlled subdomains.
|
|
105
|
-
*/
|
|
106
|
-
restrictToHosts?: readonly string[];
|
|
107
|
-
/**
|
|
108
|
-
* The GitHub token to swap for its placeholder, read per request. It reaches
|
|
109
|
-
* {@link GITHUB_HOSTS} and nowhere else. Unset, GitHub is reached
|
|
110
|
-
* anonymously like any other host. See `./github.ts`.
|
|
111
|
-
*/
|
|
112
|
-
githubToken?: () => string | undefined;
|
|
113
|
-
/** Named in log lines so one Worker's several gateways stay tellable apart. */
|
|
20
|
+
/** The pool, as the object holding it answers over RPC. */
|
|
21
|
+
export interface CredentialLease {
|
|
22
|
+
lead(): Promise<Lead>;
|
|
23
|
+
spend(id: string, resetAt: number): Promise<Lead>;
|
|
24
|
+
reject(id: string): Promise<Lead>;
|
|
25
|
+
}
|
|
26
|
+
export interface ClaudeCodeEgressOptions {
|
|
27
|
+
/** The pool `call` leases from. Default: the sandbox it came from, which holds one through {@link credentialHost}. */
|
|
28
|
+
pool?: (call: EgressCall) => CredentialLease;
|
|
114
29
|
label?: string;
|
|
115
30
|
now?: () => number;
|
|
116
31
|
}
|
|
32
|
+
/** The egress rule of a sandbox that runs Claude Code. */
|
|
33
|
+
export declare function claudeCodeEgress(options?: ClaudeCodeEgressOptions): SandboxEgressRule;
|
|
117
34
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* A real object rather than a cast: `Fetcher` requires `connect` as well as
|
|
121
|
-
* `fetch`, and container egress is HTTP only, so `connect` throws a sentence
|
|
122
|
-
* naming why instead of being absent. If a future `computerd` ever opens a raw
|
|
123
|
-
* socket through this policy, the failure says so rather than arriving as
|
|
124
|
-
* `undefined is not a function` several frames away.
|
|
35
|
+
* The pool a sandbox object holds, for its RPCs to answer {@link CredentialLease}
|
|
36
|
+
* with — and `admit`, which a session start calls: a mark lasts one session.
|
|
125
37
|
*/
|
|
126
|
-
export declare function
|
|
38
|
+
export declare function credentialHost(options: {
|
|
39
|
+
storage: DurableObjectStorage;
|
|
40
|
+
credentials: () => readonly string[];
|
|
41
|
+
key?: string;
|
|
42
|
+
}): CredentialLease & {
|
|
43
|
+
admit(): Promise<Lead>;
|
|
44
|
+
};
|