@dynamicagents/plugins 0.11.1 → 0.13.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 +59 -79
- package/dist/browser/index.d.ts +0 -2
- package/dist/browser/index.js +21 -27
- package/dist/claude-code/cards.d.ts +53 -0
- package/dist/claude-code/cards.js +272 -0
- package/dist/claude-code/config.d.ts +73 -31
- package/dist/claude-code/config.js +0 -2
- package/dist/claude-code/credentials.d.ts +21 -9
- package/dist/claude-code/credentials.js +43 -9
- package/dist/claude-code/egress.d.ts +15 -14
- package/dist/claude-code/egress.js +51 -14
- package/dist/claude-code/events.d.ts +153 -9
- package/dist/claude-code/events.js +289 -58
- package/dist/claude-code/github.d.ts +39 -0
- package/dist/claude-code/github.js +82 -0
- package/dist/claude-code/index.d.ts +24 -80
- package/dist/claude-code/index.js +19 -234
- package/dist/claude-code/model.d.ts +152 -0
- package/dist/claude-code/model.js +342 -0
- package/dist/claude-code/run.d.ts +283 -92
- package/dist/claude-code/run.js +620 -114
- package/dist/claude-code/session.d.ts +131 -0
- package/dist/claude-code/session.js +285 -0
- package/dist/claude-code/spec.d.ts +46 -0
- package/dist/claude-code/spec.js +83 -0
- package/dist/claude-code/verified.d.ts +15 -0
- package/dist/claude-code/verified.js +15 -0
- package/dist/computer/context.d.ts +19 -0
- package/dist/computer/context.js +222 -0
- package/dist/computer/file-lock.d.ts +5 -0
- package/dist/computer/file-lock.js +40 -0
- package/dist/computer/gate.d.ts +12 -12
- package/dist/computer/gate.js +12 -12
- package/dist/computer/index.d.ts +57 -255
- package/dist/computer/index.js +98 -1039
- package/dist/computer/paths.d.ts +47 -22
- package/dist/computer/paths.js +64 -49
- package/dist/computer/proxy.d.ts +45 -0
- package/dist/computer/proxy.js +234 -0
- package/dist/computer/render.d.ts +27 -38
- package/dist/computer/render.js +32 -65
- package/dist/computer/tools-exec.d.ts +3 -0
- package/dist/computer/tools-exec.js +268 -0
- package/dist/computer/tools-file.d.ts +12 -0
- package/dist/computer/tools-file.js +64 -0
- package/dist/computer/tools-grep.d.ts +3 -0
- package/dist/computer/tools-grep.js +129 -0
- package/dist/repo/checkout.d.ts +35 -6
- package/dist/repo/checkout.js +64 -6
- package/dist/repo/context.d.ts +135 -0
- package/dist/repo/context.js +582 -0
- package/dist/repo/index.d.ts +79 -57
- package/dist/repo/index.js +118 -1112
- package/dist/repo/tools-checks.d.ts +3 -0
- package/dist/repo/tools-checks.js +112 -0
- package/dist/repo/tools-clone.d.ts +4 -0
- package/dist/repo/tools-clone.js +192 -0
- package/dist/repo/tools-pr.d.ts +14 -0
- package/dist/repo/tools-pr.js +196 -0
- package/dist/repo/tools-review.d.ts +12 -0
- package/dist/repo/tools-review.js +393 -0
- package/dist/repo/tools-worktree.d.ts +4 -0
- package/dist/repo/tools-worktree.js +305 -0
- package/dist/repo/tools-worktrees.d.ts +12 -0
- package/dist/repo/tools-worktrees.js +48 -0
- package/dist/repo/url.d.ts +2 -2
- package/dist/repo/url.js +7 -7
- package/dist/scratch/index.d.ts +4 -4
- package/dist/scratch/index.js +12 -11
- package/dist/{computer → workspace}/advisory.d.ts +33 -20
- package/dist/{computer → workspace}/advisory.js +32 -7
- package/dist/workspace/ca-trust.d.ts +95 -0
- package/dist/workspace/ca-trust.js +312 -0
- package/dist/workspace/container-deps.d.ts +54 -0
- package/dist/workspace/container-deps.js +224 -0
- package/dist/workspace/container-fault.d.ts +38 -0
- package/dist/workspace/container-fault.js +73 -0
- package/dist/workspace/exec.d.ts +94 -0
- package/dist/workspace/exec.js +153 -0
- package/dist/workspace/format.d.ts +36 -0
- package/dist/workspace/format.js +57 -0
- package/dist/workspace/git-host.d.ts +79 -0
- package/dist/workspace/git-host.js +226 -0
- package/dist/workspace/git-identity.d.ts +57 -0
- package/dist/workspace/git-identity.js +147 -0
- package/dist/workspace/index.d.ts +43 -47
- package/dist/workspace/index.js +52 -112
- package/dist/workspace/install-job.d.ts +121 -0
- package/dist/workspace/install-job.js +866 -0
- package/dist/{computer → workspace}/install.d.ts +15 -19
- package/dist/{computer → workspace}/install.js +8 -11
- package/dist/workspace/object.d.ts +502 -0
- package/dist/workspace/object.js +1427 -0
- package/dist/workspace/open.d.ts +95 -0
- package/dist/workspace/open.js +63 -0
- package/dist/workspace/read.d.ts +16 -0
- package/dist/workspace/read.js +22 -0
- package/dist/workspace/shell.d.ts +45 -0
- package/dist/workspace/shell.js +80 -0
- package/dist/workspace/sync.d.ts +111 -0
- package/dist/workspace/sync.js +124 -0
- package/dist/workspace/wake.d.ts +19 -0
- package/dist/workspace/wake.js +10 -0
- package/package.json +28 -42
- package/dist/arc-agi/analysis.d.ts +0 -336
- package/dist/arc-agi/analysis.js +0 -829
- package/dist/arc-agi/client.d.ts +0 -82
- package/dist/arc-agi/client.js +0 -113
- package/dist/arc-agi/game-tools.d.ts +0 -25
- package/dist/arc-agi/game-tools.js +0 -26
- package/dist/arc-agi/index.d.ts +0 -42
- package/dist/arc-agi/index.js +0 -175
- package/dist/arc-agi/main-agent.d.ts +0 -27
- package/dist/arc-agi/main-agent.js +0 -65
- package/dist/arc-agi/recipe.d.ts +0 -51
- package/dist/arc-agi/recipe.js +0 -100
- package/dist/arc-agi/scorecard.d.ts +0 -118
- package/dist/arc-agi/scorecard.js +0 -163
- package/dist/arc-agi/soul.d.ts +0 -40
- package/dist/arc-agi/soul.js +0 -90
- package/dist/arc-agi/store.d.ts +0 -154
- package/dist/arc-agi/store.js +0 -216
- package/dist/arc-agi/tools.d.ts +0 -36
- package/dist/arc-agi/tools.js +0 -576
- package/dist/arc-agi/types.d.ts +0 -212
- package/dist/arc-agi/types.js +0 -19
- package/dist/claude-code/recipe.d.ts +0 -53
- package/dist/claude-code/recipe.js +0 -100
- package/dist/computer/read.d.ts +0 -137
- package/dist/computer/read.js +0 -203
- package/dist/recall/index.d.ts +0 -99
- package/dist/recall/index.js +0 -171
- package/dist/triage/index.d.ts +0 -355
- package/dist/triage/index.js +0 -302
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Everything one
|
|
2
|
+
* Everything one deployment of this plugin tunes, in one shape.
|
|
3
3
|
*
|
|
4
4
|
* Config at instantiation rather than an `env` argument, for the reason the
|
|
5
5
|
* whole package works this way: `Env` is the ambient interface `wrangler types`
|
|
@@ -14,8 +14,16 @@
|
|
|
14
14
|
* the exact failure this option exists to end. The CLI validates the value too,
|
|
15
15
|
* but it does so by exiting 1 with a message on stderr, and stderr reaches an
|
|
16
16
|
* operator far less reliably than a type error reaches a developer.
|
|
17
|
+
*
|
|
18
|
+
* Two of these are worth knowing about before picking one. `default` is not
|
|
19
|
+
* among the choices the pinned CLI documents, and is accepted and echoed back as
|
|
20
|
+
* `permissionMode: "default"` anyway — so it stays. `manual` is the reverse: the
|
|
21
|
+
* CLI documents it and no session here has been run under it, so it is spellable
|
|
22
|
+
* rather than recommended, and whatever it prompts for the headless path denies.
|
|
23
|
+
* See {@link ClaudeCodeConfig.permissionMode} for why the default is the
|
|
24
|
+
* permissive one.
|
|
17
25
|
*/
|
|
18
|
-
export type PermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" | "auto";
|
|
26
|
+
export type PermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" | "manual" | "auto";
|
|
19
27
|
/**
|
|
20
28
|
* What a session runs under when a deployment says nothing.
|
|
21
29
|
*
|
|
@@ -25,6 +33,21 @@ export type PermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "
|
|
|
25
33
|
* {@link ClaudeCodeConfig.permissionMode}.
|
|
26
34
|
*/
|
|
27
35
|
export declare const DEFAULT_PERMISSION_MODE: PermissionMode;
|
|
36
|
+
/**
|
|
37
|
+
* The effort levels `claude --effort` accepts.
|
|
38
|
+
*
|
|
39
|
+
* Spelled out for a sharper reason than {@link PermissionMode}: an unrecognised
|
|
40
|
+
* permission mode exits 1, but an unrecognised effort is **warned about on
|
|
41
|
+
* stderr and then ignored**, leaving the session at the model's default. A typo
|
|
42
|
+
* here does not fail, it quietly buys nothing — and stderr from a headless
|
|
43
|
+
* session reaches an operator far less reliably than a type error reaches a
|
|
44
|
+
* developer.
|
|
45
|
+
*
|
|
46
|
+
* A model that does not carry a level is the same silence: the CLI clamps down
|
|
47
|
+
* to `high` rather than refusing, so asking for more than a model offers is
|
|
48
|
+
* indistinguishable from asking for `high`.
|
|
49
|
+
*/
|
|
50
|
+
export type EffortLevel = "low" | "medium" | "high" | "xhigh" | "max";
|
|
28
51
|
export interface ClaudeCodeConfig {
|
|
29
52
|
/**
|
|
30
53
|
* The credential pool, in priority order — index 0 is tried first.
|
|
@@ -46,29 +69,31 @@ export interface ClaudeCodeConfig {
|
|
|
46
69
|
* refused with the reset time.
|
|
47
70
|
*/
|
|
48
71
|
credentials: () => readonly string[];
|
|
49
|
-
/**
|
|
50
|
-
* Which workspace this agent's sessions run in.
|
|
51
|
-
*
|
|
52
|
-
* Resolved on the **parent**, where the verified caller is known: core
|
|
53
|
-
* dispatches `resolveRuntime` to the plugin that declared the subtask type,
|
|
54
|
-
* which is this one, and the value it writes is how the subagent facet learns
|
|
55
|
-
* which Durable Object holds its checkout. A facet cannot work this out for
|
|
56
|
-
* itself — `callerKey()` throws there by design — and it is deliberately not a
|
|
57
|
-
* subtask param, because a model-authored workspace name would let a model
|
|
58
|
-
* name somebody else's.
|
|
59
|
-
*/
|
|
60
|
-
workspaceName: () => string;
|
|
61
72
|
/** Which model the session runs. Unset, Claude Code picks its own default. */
|
|
62
73
|
model?: string;
|
|
63
74
|
/**
|
|
64
|
-
*
|
|
75
|
+
* How hard the session's model thinks, per turn.
|
|
76
|
+
*
|
|
77
|
+
* Unset, the model's own default applies, which is `high` for the frontier
|
|
78
|
+
* models this plugin exists to reach. Raising it buys depth at a multiple of
|
|
79
|
+
* the spend, per turn — so the level decides how many sessions a credential
|
|
80
|
+
* holds, not just how well one thinks. The README's Costs section carries the
|
|
81
|
+
* multiples and the sizing they imply.
|
|
82
|
+
*
|
|
83
|
+
* Not to be confused with the agent's own reasoning effort, which belongs to
|
|
84
|
+
* a different model on the Worker side. This one reaches only the CLI in the
|
|
85
|
+
* container.
|
|
86
|
+
*/
|
|
87
|
+
effort?: EffortLevel;
|
|
88
|
+
/**
|
|
89
|
+
* Caps on Claude Code's own subagent tree.
|
|
65
90
|
*
|
|
66
|
-
* Advisory
|
|
67
|
-
* the
|
|
68
|
-
*
|
|
91
|
+
* Advisory, and the distinction is the reason there is no turn ceiling beside
|
|
92
|
+
* them — see the README on what this package deliberately does not do. That
|
|
93
|
+
* tree is invisible to the agent running the session, so it multiplies
|
|
94
|
+
* whatever these say; {@link timeoutMs} is the ceiling that actually holds, because the
|
|
95
|
+
* container runtime enforces it.
|
|
69
96
|
*/
|
|
70
|
-
maxTurns?: number;
|
|
71
|
-
/** Caps on Claude Code's own subagent tree. Advisory, like `maxTurns`. */
|
|
72
97
|
maxSubagentDepth?: number;
|
|
73
98
|
maxConcurrentSubagents?: number;
|
|
74
99
|
/**
|
|
@@ -98,6 +123,9 @@ export interface ClaudeCodeConfig {
|
|
|
98
123
|
*
|
|
99
124
|
* See {@link file://./run.ts buildLaunch} for the root guard this mode trips,
|
|
100
125
|
* which is the other half of making it work.
|
|
126
|
+
*
|
|
127
|
+
* A run may set its own, over this one: a planning run's is `plan` — see
|
|
128
|
+
* {@link file://./model.ts ClaudeCodeModelOptions.permissionMode}.
|
|
101
129
|
*/
|
|
102
130
|
permissionMode?: PermissionMode;
|
|
103
131
|
/**
|
|
@@ -108,20 +136,20 @@ export interface ClaudeCodeConfig {
|
|
|
108
136
|
* {@link file://./egress.ts EgressConfig.restrictToHosts} for the full table
|
|
109
137
|
* and for why open is the default.
|
|
110
138
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* forge token stays on the Worker side.
|
|
139
|
+
* A restriction that leaves out `github.com` and `api.github.com` leaves out
|
|
140
|
+
* {@link githubToken} with them.
|
|
114
141
|
*/
|
|
115
142
|
restrictToHosts?: readonly string[];
|
|
116
143
|
/**
|
|
117
|
-
*
|
|
144
|
+
* GitHub as the deployment's account, for a session that pushes its branch,
|
|
145
|
+
* opens its pull request and answers its review. Read per request, on the
|
|
146
|
+
* Worker side.
|
|
118
147
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* step timeout and a 15-minute soft chunk budget.
|
|
148
|
+
* The session gets a placeholder as `GH_TOKEN` and as git's credential, and
|
|
149
|
+
* the egress gateway swaps in this token for GitHub's hosts only — see
|
|
150
|
+
* {@link file://./github.ts}. Unset, `gh` and git reach GitHub anonymously.
|
|
123
151
|
*/
|
|
124
|
-
|
|
152
|
+
githubToken?: () => string | undefined;
|
|
125
153
|
/**
|
|
126
154
|
* Ceiling on the whole session, enforced by the container runtime.
|
|
127
155
|
*
|
|
@@ -133,8 +161,22 @@ export interface ClaudeCodeConfig {
|
|
|
133
161
|
timeoutMs?: number;
|
|
134
162
|
/** Extra environment for the session. Never secrets — see the README. */
|
|
135
163
|
env?: Record<string, string>;
|
|
164
|
+
/**
|
|
165
|
+
* Who the session's own git commits are attributed to.
|
|
166
|
+
*
|
|
167
|
+
* A session has a shell and a checkout, so it commits — and it amends,
|
|
168
|
+
* rebases and cherry-picks, none of which a host can intercept the way it
|
|
169
|
+
* names an identity on a commit it makes itself. Left unset, every one of
|
|
170
|
+
* those falls through to whatever config the checkout happens to carry, which
|
|
171
|
+
* is a value frozen whenever that checkout was created.
|
|
172
|
+
*
|
|
173
|
+
* See {@link file://./run.ts buildLaunch} for why this becomes an environment
|
|
174
|
+
* rather than a `git config`.
|
|
175
|
+
*/
|
|
176
|
+
author?: {
|
|
177
|
+
name: string;
|
|
178
|
+
email: string;
|
|
179
|
+
};
|
|
136
180
|
}
|
|
137
|
-
/** Eight minutes: comfortably inside `CHUNK_SOFT_MS`, long enough to be useful. */
|
|
138
|
-
export declare const DEFAULT_WINDOW_MS: number;
|
|
139
181
|
/** Fifteen minutes, well under the coder workspace's 20-minute idle timer. */
|
|
140
182
|
export declare const DEFAULT_TIMEOUT_MS: number;
|
|
@@ -7,7 +7,5 @@
|
|
|
7
7
|
* {@link ClaudeCodeConfig.permissionMode}.
|
|
8
8
|
*/
|
|
9
9
|
export const DEFAULT_PERMISSION_MODE = "bypassPermissions";
|
|
10
|
-
/** Eight minutes: comfortably inside `CHUNK_SOFT_MS`, long enough to be useful. */
|
|
11
|
-
export const DEFAULT_WINDOW_MS = 8 * 60_000;
|
|
12
10
|
/** Fifteen minutes, well under the coder workspace's 20-minute idle timer. */
|
|
13
11
|
export const DEFAULT_TIMEOUT_MS = 15 * 60_000;
|
|
@@ -5,15 +5,12 @@
|
|
|
5
5
|
* ## Why a pool rather than a budget
|
|
6
6
|
*
|
|
7
7
|
* A Claude subscription has two limits that matter here: a rolling 5-hour
|
|
8
|
-
* session bucket and a weekly one. Neither is readable,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* terminal `result` event), so it was never a cap on spend at all — only a gate
|
|
15
|
-
* on starting. Meanwhile the bucket itself says so, precisely, the moment it is
|
|
16
|
-
* empty: Anthropic answers `429`.
|
|
8
|
+
* session bucket and a weekly one. Neither is readable, so staying under them by
|
|
9
|
+
* *estimating* spend is the wrong instrument twice over — the estimate is a
|
|
10
|
+
* guess about a bucket nobody can see, and it only moves when a run *ends*,
|
|
11
|
+
* since usage is learned from the terminal `result` event. It is never a cap on
|
|
12
|
+
* spend, only a gate on starting. The bucket itself says so precisely the moment
|
|
13
|
+
* it is empty: Anthropic answers `429`.
|
|
17
14
|
*
|
|
18
15
|
* So this module does not predict. It **detects and routes around**: the
|
|
19
16
|
* credential becomes an ordered pool, the gateway uses the first usable entry,
|
|
@@ -154,6 +151,21 @@ export type Refusal = {
|
|
|
154
151
|
kind: "transient";
|
|
155
152
|
retryAfterMs: number;
|
|
156
153
|
};
|
|
154
|
+
/**
|
|
155
|
+
* Read the client's own bucket reading as a verdict on the credential in use.
|
|
156
|
+
*
|
|
157
|
+
* `undefined` means "nothing to act on", which today is every reading — see
|
|
158
|
+
* {@link RATE_LIMIT_SPENT}. Otherwise the epoch-milliseconds the bucket refills,
|
|
159
|
+
* ready for {@link CredentialPool.spend}.
|
|
160
|
+
*
|
|
161
|
+
* Here rather than beside the parser because this is the same question
|
|
162
|
+
* {@link readRefusal} answers about a response: is this credential still worth
|
|
163
|
+
* using? The parser's job is to say what the line contained.
|
|
164
|
+
*/
|
|
165
|
+
export declare function readRateLimitEvent(info: {
|
|
166
|
+
status: string;
|
|
167
|
+
resetsAt?: number;
|
|
168
|
+
}): number | undefined;
|
|
157
169
|
/**
|
|
158
170
|
* Read a response as a verdict on the credential that produced it.
|
|
159
171
|
*
|
|
@@ -5,15 +5,12 @@
|
|
|
5
5
|
* ## Why a pool rather than a budget
|
|
6
6
|
*
|
|
7
7
|
* A Claude subscription has two limits that matter here: a rolling 5-hour
|
|
8
|
-
* session bucket and a weekly one. Neither is readable,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* terminal `result` event), so it was never a cap on spend at all — only a gate
|
|
15
|
-
* on starting. Meanwhile the bucket itself says so, precisely, the moment it is
|
|
16
|
-
* empty: Anthropic answers `429`.
|
|
8
|
+
* session bucket and a weekly one. Neither is readable, so staying under them by
|
|
9
|
+
* *estimating* spend is the wrong instrument twice over — the estimate is a
|
|
10
|
+
* guess about a bucket nobody can see, and it only moves when a run *ends*,
|
|
11
|
+
* since usage is learned from the terminal `result` event. It is never a cap on
|
|
12
|
+
* spend, only a gate on starting. The bucket itself says so precisely the moment
|
|
13
|
+
* it is empty: Anthropic answers `429`.
|
|
17
14
|
*
|
|
18
15
|
* So this module does not predict. It **detects and routes around**: the
|
|
19
16
|
* credential becomes an ordered pool, the gateway uses the first usable entry,
|
|
@@ -243,6 +240,43 @@ function resetAtFrom(value) {
|
|
|
243
240
|
const parsed = Date.parse(trimmed);
|
|
244
241
|
return Number.isNaN(parsed) ? undefined : parsed;
|
|
245
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* The `rate_limit_event` statuses that mean the bucket is empty.
|
|
245
|
+
*
|
|
246
|
+
* **Empty, and that is the finding rather than a placeholder.** The client
|
|
247
|
+
* announces its bucket on every session and the only status ever observed is
|
|
248
|
+
* {@link file://./events.ts RATE_LIMIT_OK} — so there is no known value here to
|
|
249
|
+
* put in, and putting in a guess is the expensive half of the asymmetry
|
|
250
|
+
* {@link readRefusal} is built around: reading a healthy bucket as exhausted
|
|
251
|
+
* retires a working credential for hours, and with a pool of one that is an
|
|
252
|
+
* outage. The same reasoning that leaves a 403 unclassified leaves this set
|
|
253
|
+
* empty.
|
|
254
|
+
*
|
|
255
|
+
* What fills it is the log. `run.ts` reports every change of reading, and a
|
|
256
|
+
* status that is not `allowed` is also surfaced as a progress note, so the first
|
|
257
|
+
* real one arrives named — at which point it goes in here and the pool starts
|
|
258
|
+
* rotating *before* Anthropic refuses a request rather than because it did.
|
|
259
|
+
*/
|
|
260
|
+
const RATE_LIMIT_SPENT = new Set();
|
|
261
|
+
/**
|
|
262
|
+
* Read the client's own bucket reading as a verdict on the credential in use.
|
|
263
|
+
*
|
|
264
|
+
* `undefined` means "nothing to act on", which today is every reading — see
|
|
265
|
+
* {@link RATE_LIMIT_SPENT}. Otherwise the epoch-milliseconds the bucket refills,
|
|
266
|
+
* ready for {@link CredentialPool.spend}.
|
|
267
|
+
*
|
|
268
|
+
* Here rather than beside the parser because this is the same question
|
|
269
|
+
* {@link readRefusal} answers about a response: is this credential still worth
|
|
270
|
+
* using? The parser's job is to say what the line contained.
|
|
271
|
+
*/
|
|
272
|
+
export function readRateLimitEvent(info) {
|
|
273
|
+
if (!RATE_LIMIT_SPENT.has(info.status))
|
|
274
|
+
return undefined;
|
|
275
|
+
// Seconds on the wire. A value passed through unconverted lands in 1970, and a
|
|
276
|
+
// credential marked spent until then reads as usable — the failure would be
|
|
277
|
+
// this function doing nothing, silently.
|
|
278
|
+
return info.resetsAt === undefined ? undefined : info.resetsAt * 1000;
|
|
279
|
+
}
|
|
246
280
|
/**
|
|
247
281
|
* Read a response as a verdict on the credential that produced it.
|
|
248
282
|
*
|
|
@@ -22,7 +22,8 @@ import { type CredentialStore } from "./credentials.js";
|
|
|
22
22
|
* with a placeholder; the swap happens here. A `postinstall` script in a
|
|
23
23
|
* cloned repository can read every environment variable the container has and
|
|
24
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.
|
|
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`.
|
|
26
27
|
* 2. **Any restriction that is applied is total.** Not a policy the container
|
|
27
28
|
* cooperates with; the only route out. Restriction is **off by default** —
|
|
28
29
|
* see {@link EgressConfig.restrictToHosts} for why.
|
|
@@ -33,10 +34,9 @@ import { type CredentialStore } from "./credentials.js";
|
|
|
33
34
|
*
|
|
34
35
|
* ## What this deliberately does not do
|
|
35
36
|
*
|
|
36
|
-
* It does not meter
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* itself announces the answer exactly — see the docblock in `credentials.ts`.
|
|
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
40
|
*
|
|
41
41
|
* And it does not, by default, **bound exfiltration**. The container holds the
|
|
42
42
|
* checkout, and with no restriction configured it can send it anywhere. That is
|
|
@@ -49,16 +49,11 @@ import { type CredentialStore } from "./credentials.js";
|
|
|
49
49
|
export declare const ANTHROPIC_HOST = "api.anthropic.com";
|
|
50
50
|
export interface EgressConfig {
|
|
51
51
|
/**
|
|
52
|
-
* The credential pool
|
|
52
|
+
* The credential pool — see {@link file://./config.ts ClaudeCodeConfig.credentials}.
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* swap happens here.
|
|
58
|
-
*
|
|
59
|
-
* An array of one is entirely valid and behaves as 0.5.0's single credential
|
|
60
|
-
* did — used until its bucket empties, after which requests are refused with
|
|
61
|
-
* the reset time.
|
|
54
|
+
* Called per request, so it must be cheap. This is the one place a real
|
|
55
|
+
* credential is read: the session is launched with `CREDENTIAL_PLACEHOLDER`
|
|
56
|
+
* and the swap happens here.
|
|
62
57
|
*/
|
|
63
58
|
credentials: () => readonly string[];
|
|
64
59
|
/**
|
|
@@ -109,6 +104,12 @@ export interface EgressConfig {
|
|
|
109
104
|
* restriction, the first time a host serves user-controlled subdomains.
|
|
110
105
|
*/
|
|
111
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;
|
|
112
113
|
/** Named in log lines so one Worker's several gateways stay tellable apart. */
|
|
113
114
|
label?: string;
|
|
114
115
|
now?: () => number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { credentialPool, readRefusal } from "./credentials.js";
|
|
2
|
+
import { GITHUB_HOSTS, githubAuthorization, githubError, presentsPlaceholder } from "./github.js";
|
|
2
3
|
/**
|
|
3
4
|
* `@dynamicagents/plugins/claude-code` — the container's only way out.
|
|
4
5
|
*
|
|
@@ -22,7 +23,8 @@ import { credentialPool, readRefusal } from "./credentials.js";
|
|
|
22
23
|
* with a placeholder; the swap happens here. A `postinstall` script in a
|
|
23
24
|
* cloned repository can read every environment variable the container has and
|
|
24
25
|
* 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.
|
|
26
|
+
* action, not the credential") applied to a process nobody can constrain. A
|
|
27
|
+
* GitHub token, when configured, is held the same way — see `./github.ts`.
|
|
26
28
|
* 2. **Any restriction that is applied is total.** Not a policy the container
|
|
27
29
|
* cooperates with; the only route out. Restriction is **off by default** —
|
|
28
30
|
* see {@link EgressConfig.restrictToHosts} for why.
|
|
@@ -33,10 +35,9 @@ import { credentialPool, readRefusal } from "./credentials.js";
|
|
|
33
35
|
*
|
|
34
36
|
* ## What this deliberately does not do
|
|
35
37
|
*
|
|
36
|
-
* It does not meter
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* itself announces the answer exactly — see the docblock in `credentials.ts`.
|
|
38
|
+
* It does not meter, and a budget gate must not come back: an estimate of spend
|
|
39
|
+
* is a guess about a bucket nobody can read, moves only once per run, and the
|
|
40
|
+
* bucket announces the answer exactly — see the docblock in `credentials.ts`.
|
|
40
41
|
*
|
|
41
42
|
* And it does not, by default, **bound exfiltration**. The container holds the
|
|
42
43
|
* checkout, and with no restriction configured it can send it anywhere. That is
|
|
@@ -58,9 +59,9 @@ export const ANTHROPIC_HOST = "api.anthropic.com";
|
|
|
58
59
|
* Bouncing a request back into it is never a legitimate fetch, so it is refused
|
|
59
60
|
* before any policy is consulted.
|
|
60
61
|
*
|
|
61
|
-
* A workspace that overrides `egressHost` on its `
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* A workspace that overrides `egressHost` on its `ContainerBackend` should add
|
|
63
|
+
* that name here; the default is what the backend uses when nothing says
|
|
64
|
+
* otherwise.
|
|
64
65
|
*/
|
|
65
66
|
const NEVER_ALLOWED = new Set([
|
|
66
67
|
"computer.internal",
|
|
@@ -207,6 +208,9 @@ export function claudeCodeEgress(config) {
|
|
|
207
208
|
});
|
|
208
209
|
return apiError("permission_error", `${host} is outside this workspace's egress host restriction`, 403);
|
|
209
210
|
}
|
|
211
|
+
if (config.githubToken && GITHUB_HOSTS.has(host)) {
|
|
212
|
+
return await toGitHub(request, url, config.githubToken);
|
|
213
|
+
}
|
|
210
214
|
const headers = new Headers(request.headers);
|
|
211
215
|
if (host !== ANTHROPIC_HOST) {
|
|
212
216
|
// The placeholder must not leave the boundary either. It is worthless to
|
|
@@ -241,8 +245,8 @@ export function claudeCodeEgress(config) {
|
|
|
241
245
|
* The swap, and it is the whole point of the file.
|
|
242
246
|
*
|
|
243
247
|
* `authorization` is set and `x-api-key` removed because that is the shape
|
|
244
|
-
* the sanctioned client sends
|
|
245
|
-
*
|
|
248
|
+
* the sanctioned client sends: `authorization: Bearer …` with no
|
|
249
|
+
* `x-api-key` at all. Everything else is forwarded
|
|
246
250
|
* untouched: the `anthropic-beta` list (which carries
|
|
247
251
|
* `claude-code-20250219` and `oauth-2025-04-20`, almost certainly part of
|
|
248
252
|
* what marks the request as coming from the client) and the `user-agent`
|
|
@@ -264,6 +268,39 @@ export function claudeCodeEgress(config) {
|
|
|
264
268
|
}
|
|
265
269
|
return await rotate(request, response, lead.id, lead.index);
|
|
266
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* A request to GitHub: anonymous unless it presents the placeholder,
|
|
273
|
+
* which is swapped for the real token over https only.
|
|
274
|
+
*
|
|
275
|
+
* `redirect: "manual"`, so the token rides only the request it was swapped
|
|
276
|
+
* into: a redirect goes back to the client, whose next request crosses this
|
|
277
|
+
* gateway again and is judged on its own host.
|
|
278
|
+
*/
|
|
279
|
+
const toGitHub = async (request, url, githubToken) => {
|
|
280
|
+
const headers = new Headers(request.headers);
|
|
281
|
+
const presented = presentsPlaceholder(headers.get("authorization"));
|
|
282
|
+
for (const name of CREDENTIAL_HEADERS)
|
|
283
|
+
headers.delete(name);
|
|
284
|
+
if (!presented) {
|
|
285
|
+
return await fetch(new Request(url, new Request(request, { headers })));
|
|
286
|
+
}
|
|
287
|
+
if (url.protocol !== "https:") {
|
|
288
|
+
console.warn(`[${tag}] refused a plaintext request to GitHub`, {
|
|
289
|
+
host: url.hostname,
|
|
290
|
+
method: request.method
|
|
291
|
+
});
|
|
292
|
+
return githubError(`${url.hostname} is reachable over https only; a credential is never ` +
|
|
293
|
+
"attached to a plaintext request", 403);
|
|
294
|
+
}
|
|
295
|
+
const token = githubToken();
|
|
296
|
+
if (!token) {
|
|
297
|
+
console.error(`[${tag}] a session asked for GitHub with no token set`);
|
|
298
|
+
return githubError("no GitHub token is configured for this deployment, so nothing can " +
|
|
299
|
+
"be done as its account", 401);
|
|
300
|
+
}
|
|
301
|
+
headers.set("authorization", githubAuthorization(presented, token));
|
|
302
|
+
return await fetch(new Request(url, new Request(request, { headers, redirect: "manual" })));
|
|
303
|
+
};
|
|
267
304
|
/**
|
|
268
305
|
* Read a refusal, move the lead if it earned one, and shape what the client
|
|
269
306
|
* sees so its own retry lands on the new credential.
|
|
@@ -278,10 +315,10 @@ export function claudeCodeEgress(config) {
|
|
|
278
315
|
/**
|
|
279
316
|
* Capture the whole thing, every time.
|
|
280
317
|
*
|
|
281
|
-
* **No genuine subscription-exhaustion `429` has
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
318
|
+
* **No genuine subscription-exhaustion `429` has been observed through this
|
|
319
|
+
* path** — a raw-API refusal is a different response — so the rules in
|
|
320
|
+
* `readRefusal` are inferences from the documented API rate limits. This log
|
|
321
|
+
* is the instrument that replaces
|
|
285
322
|
* them with a fact, and it costs nothing: these statuses are rare, and the
|
|
286
323
|
* body of one is a few hundred bytes.
|
|
287
324
|
*/
|