@dynamicagents/plugins 0.12.0 → 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 +58 -77
- 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 +28 -63
- package/dist/claude-code/config.js +0 -11
- package/dist/claude-code/egress.d.ts +8 -1
- package/dist/claude-code/egress.js +42 -4
- package/dist/claude-code/events.d.ts +106 -9
- package/dist/claude-code/events.js +201 -56
- package/dist/claude-code/github.d.ts +39 -0
- package/dist/claude-code/github.js +82 -0
- package/dist/claude-code/index.d.ts +16 -90
- package/dist/claude-code/index.js +14 -276
- package/dist/claude-code/model.d.ts +152 -0
- package/dist/claude-code/model.js +342 -0
- package/dist/claude-code/run.d.ts +209 -112
- package/dist/claude-code/run.js +339 -138
- 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 +1 -5
- package/dist/computer/context.js +7 -10
- package/dist/computer/file-lock.js +5 -4
- package/dist/computer/gate.d.ts +4 -4
- package/dist/computer/gate.js +5 -5
- package/dist/computer/index.d.ts +40 -258
- package/dist/computer/index.js +71 -324
- package/dist/computer/paths.d.ts +36 -16
- package/dist/computer/paths.js +42 -29
- package/dist/computer/proxy.d.ts +45 -0
- package/dist/computer/proxy.js +234 -0
- package/dist/computer/render.d.ts +1 -40
- package/dist/computer/render.js +2 -68
- package/dist/computer/tools-exec.js +12 -13
- package/dist/computer/tools-file.d.ts +9 -1
- package/dist/computer/tools-file.js +22 -82
- package/dist/computer/{tools-find.d.ts → tools-grep.d.ts} +1 -1
- package/dist/computer/tools-grep.js +129 -0
- package/dist/repo/checkout.d.ts +4 -5
- package/dist/repo/checkout.js +31 -8
- package/dist/repo/context.d.ts +35 -9
- package/dist/repo/context.js +101 -49
- package/dist/repo/index.d.ts +73 -28
- package/dist/repo/index.js +112 -96
- package/dist/repo/tools-checks.d.ts +3 -0
- package/dist/repo/tools-checks.js +112 -0
- package/dist/repo/tools-clone.js +1 -1
- package/dist/repo/tools-pr.d.ts +14 -0
- package/dist/repo/{tools-forge.js → tools-pr.js} +53 -36
- package/dist/repo/tools-review.d.ts +9 -0
- package/dist/repo/tools-review.js +67 -47
- package/dist/repo/tools-worktree.js +144 -60
- 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 +4 -4
- package/dist/{computer → workspace}/advisory.js +2 -2
- package/dist/{computer/host → workspace}/ca-trust.d.ts +8 -1
- package/dist/{computer/host → workspace}/ca-trust.js +12 -3
- package/dist/{computer/host → workspace}/container-fault.d.ts +3 -3
- package/dist/{computer/host → workspace}/container-fault.js +23 -8
- 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/{computer/host → workspace}/git-host.d.ts +21 -4
- package/dist/{computer/host → workspace}/git-host.js +72 -2
- package/dist/{computer/host → workspace}/git-identity.d.ts +1 -1
- package/dist/{computer/host → workspace}/git-identity.js +1 -1
- package/dist/workspace/index.d.ts +43 -47
- package/dist/workspace/index.js +52 -112
- package/dist/{computer/host → workspace}/install-job.d.ts +4 -4
- package/dist/{computer/host → workspace}/install-job.js +276 -176
- package/dist/{computer → workspace}/install.d.ts +8 -8
- package/dist/{computer → workspace}/install.js +3 -3
- package/dist/{computer/host/workspace.d.ts → workspace/object.d.ts} +105 -27
- package/dist/{computer/host/workspace.js → workspace/object.js} +217 -48
- 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/{computer → workspace}/shell.d.ts +5 -5
- package/dist/{computer → workspace}/shell.js +6 -6
- package/dist/{computer/host → workspace}/wake.d.ts +1 -1
- package/package.json +16 -36
- package/dist/arc-agi/analysis.d.ts +0 -322
- package/dist/arc-agi/analysis.js +0 -802
- 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 -24
- package/dist/arc-agi/game-tools.js +0 -26
- package/dist/arc-agi/index.d.ts +0 -40
- package/dist/arc-agi/index.js +0 -175
- package/dist/arc-agi/main-agent.d.ts +0 -24
- package/dist/arc-agi/main-agent.js +0 -62
- package/dist/arc-agi/recipe.d.ts +0 -48
- package/dist/arc-agi/recipe.js +0 -97
- 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 -29
- package/dist/arc-agi/soul.js +0 -79
- package/dist/arc-agi/store.d.ts +0 -153
- package/dist/arc-agi/store.js +0 -215
- package/dist/arc-agi/tools.d.ts +0 -36
- package/dist/arc-agi/tools.js +0 -574
- package/dist/arc-agi/types.d.ts +0 -211
- 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 -133
- package/dist/computer/read.js +0 -200
- package/dist/computer/tools-find.js +0 -216
- package/dist/recall/index.d.ts +0 -105
- package/dist/recall/index.js +0 -181
- package/dist/repo/tools-forge.d.ts +0 -4
- package/dist/triage/index.d.ts +0 -360
- package/dist/triage/index.js +0 -311
- /package/dist/{computer/host → workspace}/container-deps.d.ts +0 -0
- /package/dist/{computer/host → workspace}/container-deps.js +0 -0
- /package/dist/{computer/host → workspace}/sync.d.ts +0 -0
- /package/dist/{computer/host → workspace}/sync.js +0 -0
- /package/dist/{computer/host → workspace}/wake.js +0 -0
package/dist/repo/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_ALLOWED_HOSTS, parseRepo, repoLocation } from "./url.js";
|
|
2
2
|
/**
|
|
3
3
|
* The machinery every repo tool closes over: the resolved config, the git
|
|
4
|
-
* runners, and the
|
|
4
|
+
* runners, and the GitHub client.
|
|
5
5
|
*/
|
|
6
6
|
const DEFAULT_WORKDIR = "/workspace";
|
|
7
7
|
const DEFAULT_API_BASE = "https://api.github.com";
|
|
@@ -11,15 +11,15 @@ const DEFAULT_AUTHOR = {
|
|
|
11
11
|
};
|
|
12
12
|
const DEFAULT_MAX_OUTPUT_CHARS = 16_000;
|
|
13
13
|
/**
|
|
14
|
-
* Ceiling on every call this plugin makes to
|
|
14
|
+
* Ceiling on every call this plugin makes to GitHub's API.
|
|
15
15
|
*
|
|
16
16
|
* Not configurable, because it is not a tuning knob: it exists so an API that
|
|
17
|
-
* stops answering costs a
|
|
17
|
+
* stops answering costs a tool call rather than the task. These are single small
|
|
18
18
|
* requests, and thirty seconds is already generous for one.
|
|
19
19
|
*/
|
|
20
|
-
const
|
|
20
|
+
const GITHUB_TIMEOUT_MS = 30_000;
|
|
21
21
|
/**
|
|
22
|
-
* How many items one
|
|
22
|
+
* How many items one GitHub list call asks for. GitHub's maximum, and its default
|
|
23
23
|
* without this is 30.
|
|
24
24
|
*
|
|
25
25
|
* One page, never a Link-header walk: an unbounded fetch loop does not belong
|
|
@@ -28,7 +28,7 @@ const FORGE_TIMEOUT_MS = 30_000;
|
|
|
28
28
|
* presented as the whole answer — a pull request's newest review comments are on
|
|
29
29
|
* the last page, and those are exactly the ones an agent was sent to act on.
|
|
30
30
|
*/
|
|
31
|
-
export const
|
|
31
|
+
export const GITHUB_PAGE_SIZE = 100;
|
|
32
32
|
/**
|
|
33
33
|
* Where GraphQL lives, given where REST does.
|
|
34
34
|
*
|
|
@@ -47,10 +47,10 @@ export function graphqlEndpoint(apiBase) {
|
|
|
47
47
|
/**
|
|
48
48
|
* Middle-out truncation, so both the head of a diff and its tail survive.
|
|
49
49
|
*
|
|
50
|
-
* Deliberately a **copy** of
|
|
51
|
-
*
|
|
50
|
+
* Deliberately a **copy** of `/workspace`'s function of the same name, not an
|
|
51
|
+
* import. `npm run verify:exports` fails any subpath whose module graph
|
|
52
52
|
* reaches a sibling's directory, and its own advice for a shared helper is to
|
|
53
|
-
* duplicate it — installing `repo` must not drag `
|
|
53
|
+
* duplicate it — installing `repo` must not drag `/workspace` (and therefore
|
|
54
54
|
* `@cloudflare/computer`) into a consumer's bundle. Fifteen lines is a cheaper
|
|
55
55
|
* price than that coupling, and it is the same reason `exec` is injected here
|
|
56
56
|
* rather than imported.
|
|
@@ -74,7 +74,7 @@ export function truncateOutput(text, max) {
|
|
|
74
74
|
* A sibling of the computer plugin's `execLostNote` rather than an import, for
|
|
75
75
|
* the same reason {@link truncateOutput} is a copy: `verify:exports` fails any
|
|
76
76
|
* subpath whose module graph reaches a sibling's, and installing `repo` must
|
|
77
|
-
* not drag
|
|
77
|
+
* not drag `/computer` in behind it. The wording differs anyway, and the
|
|
78
78
|
* difference is the point — that plugin cannot know the lost command was git,
|
|
79
79
|
* and this one does. What a model needs after a lost `git push` is not "re-run
|
|
80
80
|
* it" but whether re-running it is *safe*.
|
|
@@ -85,7 +85,7 @@ function unreachableNote(err) {
|
|
|
85
85
|
"is known to have finished. This is infrastructure — not git, and not what " +
|
|
86
86
|
"you asked for. The checkout is durable and is exactly as you left it. " +
|
|
87
87
|
"Retry, and read what the retry says rather than assuming it starts from " +
|
|
88
|
-
"nothing: a command that had already reached
|
|
88
|
+
"nothing: a command that had already reached GitHub may have taken " +
|
|
89
89
|
"effect. Retrying is safe either way — these commands push one branch to " +
|
|
90
90
|
"the branch of the same name, and never force.");
|
|
91
91
|
}
|
|
@@ -102,13 +102,13 @@ function unreachableNote(err) {
|
|
|
102
102
|
* than waiting if it is already held. A model may emit several tool calls in one
|
|
103
103
|
* turn and the SDK runs them concurrently, so unserialised a `repo_commit` and a
|
|
104
104
|
* `repo_push` race: the commit fails, the push succeeds against the previous
|
|
105
|
-
* state, and the
|
|
105
|
+
* state, and the turn reports work that never landed.
|
|
106
106
|
*
|
|
107
107
|
* Held here rather than in {@link repoContext}'s closure, because that closure
|
|
108
|
-
* is too short-lived to be the boundary.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
108
|
+
* is too short-lived to be the boundary. The tools are built for every turn, and
|
|
109
|
+
* a sub-agent builds its own from the same config, so a per-call queue leaves a
|
|
110
|
+
* sub-agent racing its parent over one checkout — the same collision, one level
|
|
111
|
+
* up. The config object is the plugin instance, and a `WeakMap` means a
|
|
112
112
|
* discarded agent takes its queue with it.
|
|
113
113
|
*
|
|
114
114
|
* A promise chain rather than a lock, because that is all the scope needs: one
|
|
@@ -145,11 +145,10 @@ const gitQueues = new WeakMap();
|
|
|
145
145
|
*
|
|
146
146
|
* A command whose container or host never answers — a workspace object that
|
|
147
147
|
* reset under it — would otherwise hold every git command behind it for the life
|
|
148
|
-
* of the isolate
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* abandoned, and far longer than any git command here takes.
|
|
148
|
+
* of the isolate. Past this the command is reported unreachable and the queue
|
|
149
|
+
* moves on. It may still be running; a later command that meets its
|
|
150
|
+
* `index.lock` fails fast and says so. Far longer than any git command here
|
|
151
|
+
* takes.
|
|
153
152
|
*/
|
|
154
153
|
const GIT_SLOT_MS = 8 * 60_000;
|
|
155
154
|
/** `work`, rejected once {@link GIT_SLOT_MS} passes without it settling. */
|
|
@@ -162,7 +161,43 @@ function withinSlot(work) {
|
|
|
162
161
|
work.catch(() => { });
|
|
163
162
|
return Promise.race([work, expired]).finally(() => clearTimeout(timer));
|
|
164
163
|
}
|
|
165
|
-
|
|
164
|
+
/**
|
|
165
|
+
* How long an action that calls GitHub may run before Think gives up on it.
|
|
166
|
+
*
|
|
167
|
+
* An action's default is thirty seconds, which is {@link GITHUB_TIMEOUT_MS}
|
|
168
|
+
* alone — and a thread reply is three GitHub calls, after an `origin` read that
|
|
169
|
+
* waits its turn in the git queue.
|
|
170
|
+
*/
|
|
171
|
+
export const GITHUB_ACTION_TIMEOUT_MS = 120_000;
|
|
172
|
+
/**
|
|
173
|
+
* The task an action runs for, which leads its idempotency key.
|
|
174
|
+
*
|
|
175
|
+
* Think replays a settled key for as long as it keeps the row
|
|
176
|
+
* (`actionLedgerRetention`), so a key without the task would replay, not post,
|
|
177
|
+
* the same comment in a later task. Every turn core runs carries a task id, so
|
|
178
|
+
* a missing one is a wiring fault — and keying without it would be the silent
|
|
179
|
+
* version of that fault.
|
|
180
|
+
*/
|
|
181
|
+
export function actionTask(ctx) {
|
|
182
|
+
const task = ctx.agent.activeTurnMetadata?.taskId;
|
|
183
|
+
if (typeof task !== "string")
|
|
184
|
+
throw new Error("this turn carries no task id, so a GitHub write cannot be keyed to it — " +
|
|
185
|
+
"the repo plugin's actions run only in turns core started for a task");
|
|
186
|
+
return task;
|
|
187
|
+
}
|
|
188
|
+
/** A hex SHA-256, so a key names a body without carrying it. */
|
|
189
|
+
export async function sha256(text) {
|
|
190
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
191
|
+
return [...new Uint8Array(digest)]
|
|
192
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
193
|
+
.join("");
|
|
194
|
+
}
|
|
195
|
+
export function repoContext(config,
|
|
196
|
+
/**
|
|
197
|
+
* The running sub-agent's `runtime()`, read per command. Forwarded to every
|
|
198
|
+
* `exec`; see {@link RepoExec}'s `runtime` option.
|
|
199
|
+
*/
|
|
200
|
+
runtime = () => undefined) {
|
|
166
201
|
const workdir = config.workdir ?? DEFAULT_WORKDIR;
|
|
167
202
|
const apiBase = config.apiBase ?? DEFAULT_API_BASE;
|
|
168
203
|
const graphqlUrl = graphqlEndpoint(apiBase);
|
|
@@ -194,7 +229,7 @@ export function repoContext(config, runtime) {
|
|
|
194
229
|
}
|
|
195
230
|
};
|
|
196
231
|
/**
|
|
197
|
-
* The
|
|
232
|
+
* The GitHub credential, or the reason there is none.
|
|
198
233
|
*
|
|
199
234
|
* `config.token` is the host's thunk, and an unresolvable secret throws at the
|
|
200
235
|
* moment of use rather than at startup. Every caller here has a sentence to
|
|
@@ -208,7 +243,7 @@ export function repoContext(config, runtime) {
|
|
|
208
243
|
}
|
|
209
244
|
catch (err) {
|
|
210
245
|
return {
|
|
211
|
-
failure: `the
|
|
246
|
+
failure: `the GitHub credential could not be read: ${String(err)}. That is the ` +
|
|
212
247
|
`host's configuration rather than anything you passed — report it ` +
|
|
213
248
|
`rather than working around it.`
|
|
214
249
|
};
|
|
@@ -229,8 +264,8 @@ export function repoContext(config, runtime) {
|
|
|
229
264
|
* of an investigation and findable only by timestamp.
|
|
230
265
|
*
|
|
231
266
|
* The token is scrubbed rather than trusted. It has no route into a container
|
|
232
|
-
* command, and the one channel that could carry it is the
|
|
233
|
-
* body, which {@link
|
|
267
|
+
* command, and the one channel that could carry it is the GitHub API's error
|
|
268
|
+
* body, which {@link github} logs through this same function. So stderr *should*
|
|
234
269
|
* be clean — but "should be" is not the standard for something that writes a
|
|
235
270
|
* credential into a log that outlives the request, and the scrub costs a
|
|
236
271
|
* `split`/`join` on a path that already failed.
|
|
@@ -308,7 +343,7 @@ export function repoContext(config, runtime) {
|
|
|
308
343
|
*/
|
|
309
344
|
const shell = (script, cwd, vars = {}) => run(script, () => ({
|
|
310
345
|
cwd,
|
|
311
|
-
runtime,
|
|
346
|
+
runtime: runtime(),
|
|
312
347
|
env: { GIT_TERMINAL_PROMPT: "0", ...vars }
|
|
313
348
|
}));
|
|
314
349
|
const plain = (args, cwd, vars = {}) => shell(`git ${args}`, cwd, vars);
|
|
@@ -366,9 +401,9 @@ export function repoContext(config, runtime) {
|
|
|
366
401
|
/** Push one branch to the same-named branch on the remote. Never forced. */
|
|
367
402
|
const pushBranch = (dir, url, branch) => runGit(() => config.git.push({ url, dir, branch, allowedHosts }));
|
|
368
403
|
/**
|
|
369
|
-
* One call to
|
|
404
|
+
* One call to GitHub's API, from the Worker.
|
|
370
405
|
*
|
|
371
|
-
* Every tool here that talks to
|
|
406
|
+
* Every tool here that talks to GitHub goes through this, so the credential
|
|
372
407
|
* stays on the Worker's side of the boundary and the bound, the header set and
|
|
373
408
|
* the refusal to let an unreadable body replace an explanatory status are
|
|
374
409
|
* written once.
|
|
@@ -377,14 +412,14 @@ export function repoContext(config, runtime) {
|
|
|
377
412
|
* its own sentence to add: a POST whose answer never arrived may have been
|
|
378
413
|
* received, which a GET has no reason to warn about.
|
|
379
414
|
*/
|
|
380
|
-
const
|
|
415
|
+
const github = (tool, path, init) => githubAt(tool, `${apiBase}${path}`, init);
|
|
381
416
|
/**
|
|
382
417
|
* The same call, against a URL rather than a path under {@link apiBase}.
|
|
383
418
|
*
|
|
384
419
|
* GraphQL needs it: on Enterprise the REST base carries `/api/v3` and GraphQL
|
|
385
420
|
* does not live under it — see {@link graphqlEndpoint}.
|
|
386
421
|
*/
|
|
387
|
-
const
|
|
422
|
+
const githubAt = async (tool, url, init) => {
|
|
388
423
|
const secret = credential();
|
|
389
424
|
if ("failure" in secret) {
|
|
390
425
|
logFailure(tool, { stderr: secret.failure });
|
|
@@ -401,7 +436,7 @@ export function repoContext(config, runtime) {
|
|
|
401
436
|
"user-agent": "da-coder"
|
|
402
437
|
},
|
|
403
438
|
...(init ? { body: JSON.stringify(init.body) } : {}),
|
|
404
|
-
signal: AbortSignal.timeout(
|
|
439
|
+
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS)
|
|
405
440
|
});
|
|
406
441
|
}
|
|
407
442
|
catch (err) {
|
|
@@ -419,7 +454,7 @@ export function repoContext(config, runtime) {
|
|
|
419
454
|
return { ok: true, data: await response.json().catch(() => ({})) };
|
|
420
455
|
};
|
|
421
456
|
/**
|
|
422
|
-
* One GraphQL query or mutation, over {@link
|
|
457
|
+
* One GraphQL query or mutation, over {@link github} so the credential, the
|
|
423
458
|
* bound, the headers and the scrubbed failure log stay written once.
|
|
424
459
|
*
|
|
425
460
|
* **A failed GraphQL query answers `200`.** The errors are in the body, so
|
|
@@ -434,8 +469,8 @@ export function repoContext(config, runtime) {
|
|
|
434
469
|
* `repo_issue_view` reads, and a review *request* naming an app is invisible to
|
|
435
470
|
* REST — see {@link file://./tools-review.ts REVIEW_REQUESTS_QUERY}.
|
|
436
471
|
*/
|
|
437
|
-
const
|
|
438
|
-
const answered = await
|
|
472
|
+
const githubGraphql = async (tool, query, variables) => {
|
|
473
|
+
const answered = await githubAt(tool, graphqlUrl, {
|
|
439
474
|
method: "POST",
|
|
440
475
|
body: { query, variables }
|
|
441
476
|
});
|
|
@@ -455,7 +490,7 @@ export function repoContext(config, runtime) {
|
|
|
455
490
|
return { ok: true, data: body.data };
|
|
456
491
|
};
|
|
457
492
|
/**
|
|
458
|
-
* Which repository the tools that read
|
|
493
|
+
* Which repository the tools that read GitHub are talking about.
|
|
459
494
|
*
|
|
460
495
|
* The checkout's own origin, never a URL the model supplies. Three reasons, in
|
|
461
496
|
* order of how much they matter: the origin has already been through the host
|
|
@@ -465,7 +500,7 @@ export function repoContext(config, runtime) {
|
|
|
465
500
|
* "issue 42" almost always means the repository it is working in, so the extra
|
|
466
501
|
* parameter would mostly be an extra way to be wrong.
|
|
467
502
|
*/
|
|
468
|
-
const
|
|
503
|
+
const githubRepo = async (dir) => {
|
|
469
504
|
const { remote, unreachable } = await origin(dir);
|
|
470
505
|
if (unreachable)
|
|
471
506
|
return { refusal: bounded(unreachable) };
|
|
@@ -491,15 +526,32 @@ export function repoContext(config, runtime) {
|
|
|
491
526
|
* `repo_clone` only ever writes after the allowlist has passed.
|
|
492
527
|
*/
|
|
493
528
|
const origin = async (dir) => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
529
|
+
let url;
|
|
530
|
+
if (config.git.origin) {
|
|
531
|
+
let read;
|
|
532
|
+
try {
|
|
533
|
+
read = await config.git.origin(dir);
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
return {
|
|
537
|
+
unreachable: `the workspace could not be reached to read ${dir}'s origin: ${String(err)}`
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (!read)
|
|
541
|
+
return {};
|
|
542
|
+
url = read.trim();
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
const result = await plain("remote get-url origin", dir);
|
|
546
|
+
// Two different nothings. `repo_push`'s answer to "no origin here" is to
|
|
547
|
+
// tell the model to clone the repository first — advice that is actively
|
|
548
|
+
// wrong when the truth is that no command ran at all.
|
|
549
|
+
if (result.unreachable)
|
|
550
|
+
return { unreachable: result.stderr };
|
|
551
|
+
if (!result.success)
|
|
552
|
+
return {};
|
|
553
|
+
url = result.stdout.trim();
|
|
554
|
+
}
|
|
503
555
|
const location = repoLocation(url);
|
|
504
556
|
if (!location || !allowedHosts.includes(location.host))
|
|
505
557
|
return {};
|
|
@@ -523,8 +575,8 @@ export function repoContext(config, runtime) {
|
|
|
523
575
|
fetchOrigin,
|
|
524
576
|
pushBranch,
|
|
525
577
|
origin,
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
578
|
+
github,
|
|
579
|
+
githubGraphql,
|
|
580
|
+
githubRepo
|
|
529
581
|
};
|
|
530
582
|
}
|
package/dist/repo/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ToolSet } from "ai";
|
|
2
1
|
import type { AgentPlugin } from "@dynamicagents/core";
|
|
3
2
|
/**
|
|
4
3
|
* The URL parsing, re-exported from the one entry point.
|
|
@@ -13,8 +12,8 @@ export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
|
13
12
|
* `@dynamicagents/plugins/repo` — clone, commit, push, open a pull request.
|
|
14
13
|
*
|
|
15
14
|
* Layered over a container rather than owning one: it needs a shell with `git`
|
|
16
|
-
* on it, and `@dynamicagents/plugins/
|
|
17
|
-
* `
|
|
15
|
+
* on it, and `@dynamicagents/plugins/workspace` provides exactly that through
|
|
16
|
+
* `workspaceExec`. Passing `exec` in rather than importing that plugin keeps the
|
|
18
17
|
* two independent — a host with its own container can use this against that
|
|
19
18
|
* instead, and the tests here need no container at all.
|
|
20
19
|
*
|
|
@@ -27,7 +26,7 @@ export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
|
27
26
|
* none of it needs to authenticate.
|
|
28
27
|
*
|
|
29
28
|
* That split is the one thing to preserve when changing this file: if an
|
|
30
|
-
* operation talks to
|
|
29
|
+
* operation talks to GitHub it does not belong on `exec`, and if it does not,
|
|
31
30
|
* it has no business anywhere else.
|
|
32
31
|
*
|
|
33
32
|
* It is absolute rather than careful because a narrower version does not hold.
|
|
@@ -41,9 +40,9 @@ export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
|
41
40
|
* the length of one command is readable at `/proc/<pid>/environ` by anything
|
|
42
41
|
* else in the container.
|
|
43
42
|
*
|
|
44
|
-
* Two further rules, which are about
|
|
43
|
+
* Two further rules, which are about GitHub rather than the container:
|
|
45
44
|
*
|
|
46
|
-
* 1. **
|
|
45
|
+
* 1. **GitHub's hosts are an allowlist, checked before anything runs.** A clone URL is
|
|
47
46
|
* model input — a repository README, an issue body, or a page fetched by a
|
|
48
47
|
* co-installed browser plugin is enough to choose it — and a credential
|
|
49
48
|
* offered to a host of the model's choosing is the whole game. So the URL's
|
|
@@ -52,7 +51,7 @@ export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
|
52
51
|
* check to the moment the token would actually be handed over. `origin` is
|
|
53
52
|
* re-derived and re-checked on every push rather than remembered, because the
|
|
54
53
|
* checkout's `.git/config` is a file the container can rewrite.
|
|
55
|
-
* 2. **The
|
|
54
|
+
* 2. **The GitHub API is called from the Worker**, so the credential that can
|
|
56
55
|
* write to a repository through the API never crosses the boundary either.
|
|
57
56
|
* Every tool that reads or writes through it resolves the repository from the
|
|
58
57
|
* checkout's own origin rather than from a parameter, so there is no way to
|
|
@@ -67,23 +66,20 @@ export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
|
67
66
|
* repository. Whatever the token can reach, an agent talked into naming it can
|
|
68
67
|
* reach. See the README — the token wants to be fine-grained.
|
|
69
68
|
*/
|
|
70
|
-
/**
|
|
71
|
-
export declare const REPO_FAMILY = "repo";
|
|
72
|
-
/** Run one command in the host's container. Matches `computerExec`'s shape. */
|
|
69
|
+
/** Run one command in the host's container. Matches `workspaceExec`'s shape. */
|
|
73
70
|
export type RepoExec = (command: string, options?: {
|
|
74
71
|
cwd?: string;
|
|
75
72
|
env?: Record<string, string | undefined>;
|
|
76
73
|
timeout?: number;
|
|
77
74
|
/**
|
|
78
|
-
* The
|
|
75
|
+
* The running sub-agent's `runtime()`, forwarded **opaquely**.
|
|
79
76
|
*
|
|
80
|
-
* This plugin never looks inside it. A
|
|
81
|
-
* container its parent prepared, and the key for that lives in the
|
|
82
|
-
*
|
|
77
|
+
* This plugin never looks inside it. A sub-agent has to reach the
|
|
78
|
+
* container its parent prepared, and the key for that lives in what the
|
|
79
|
+
* parent's `prepare` returned — but decoding it is the container plugin's
|
|
83
80
|
* business, not git's. Passing it through untouched is what lets the two
|
|
84
|
-
* plugins compose
|
|
85
|
-
*
|
|
86
|
-
* injected rather than imported.
|
|
81
|
+
* plugins compose without either importing the other, which is the whole
|
|
82
|
+
* reason `exec` is injected rather than imported.
|
|
87
83
|
*/
|
|
88
84
|
runtime?: unknown;
|
|
89
85
|
}) => Promise<{
|
|
@@ -93,7 +89,7 @@ export type RepoExec = (command: string, options?: {
|
|
|
93
89
|
exitCode: number;
|
|
94
90
|
}>;
|
|
95
91
|
/**
|
|
96
|
-
* The three git operations that need the
|
|
92
|
+
* The three git operations that need the GitHub token, run by the host.
|
|
97
93
|
*
|
|
98
94
|
* Injected for the same reason {@link RepoExec} is — this plugin owns the
|
|
99
95
|
* policy, not the plumbing — but the split between the two is not arbitrary. It
|
|
@@ -102,7 +98,7 @@ export type RepoExec = (command: string, options?: {
|
|
|
102
98
|
* keeps its secret and never touches the container at all.
|
|
103
99
|
*
|
|
104
100
|
* That is why `clone`, `fetch` and `push` are the whole interface. They are
|
|
105
|
-
* exactly the operations that talk to
|
|
101
|
+
* exactly the operations that talk to GitHub. `status`, `diff`, `add`,
|
|
106
102
|
* `commit` and `checkout` are local, need nothing to authenticate with, and stay
|
|
107
103
|
* on `exec` where they are cheap and where the model can see them work.
|
|
108
104
|
*
|
|
@@ -155,6 +151,14 @@ export interface RepoGit {
|
|
|
155
151
|
branch: string;
|
|
156
152
|
allowedHosts: string[];
|
|
157
153
|
}): Promise<RepoGitResult>;
|
|
154
|
+
/**
|
|
155
|
+
* The checkout's `origin` URL, read on the host's side; `undefined` for none.
|
|
156
|
+
*
|
|
157
|
+
* Optional. Set, the GitHub tools resolve their repository without the
|
|
158
|
+
* container, so an agent polling a pull request does not keep one running.
|
|
159
|
+
* Unset, the origin is read through {@link RepoConfig.exec}.
|
|
160
|
+
*/
|
|
161
|
+
origin?(dir: string): Promise<string | undefined>;
|
|
158
162
|
}
|
|
159
163
|
/** What a {@link RepoGit} operation reports. */
|
|
160
164
|
export type RepoGitResult = {
|
|
@@ -169,14 +173,14 @@ export interface RepoConfig {
|
|
|
169
173
|
/**
|
|
170
174
|
* Runs commands in the container holding the checkout.
|
|
171
175
|
*
|
|
172
|
-
* Uncredentialed, always. Nothing this runs is ever given the
|
|
176
|
+
* Uncredentialed, always. Nothing this runs is ever given the GitHub token —
|
|
173
177
|
* see {@link RepoGit} for the operations that need one.
|
|
174
178
|
*/
|
|
175
179
|
exec: RepoExec;
|
|
176
180
|
/** Runs the three credentialed operations, on the host's side of the boundary. */
|
|
177
181
|
git: RepoGit;
|
|
178
182
|
/**
|
|
179
|
-
* A
|
|
183
|
+
* A GitHub token with contents+pull-request write. A thunk so a rotated secret
|
|
180
184
|
* is picked up without rebuilding the plugin list.
|
|
181
185
|
*/
|
|
182
186
|
token: () => string;
|
|
@@ -205,9 +209,9 @@ export interface RepoConfig {
|
|
|
205
209
|
* 16,000 non-ASCII characters is three times that many UTF-8 bytes.
|
|
206
210
|
*
|
|
207
211
|
* `repo_diff` is the main input for an agent that
|
|
208
|
-
* reviews rather than writes — a
|
|
209
|
-
*
|
|
210
|
-
*
|
|
212
|
+
* reviews rather than writes — a coder whose sub-agents hold the shell — and
|
|
213
|
+
* an unbounded diff on a large change is precisely the context blowup that
|
|
214
|
+
* design exists to prevent.
|
|
211
215
|
*/
|
|
212
216
|
maxOutputChars?: number;
|
|
213
217
|
/**
|
|
@@ -228,7 +232,7 @@ export interface RepoConfig {
|
|
|
228
232
|
* not — a host that stored the path alongside its install state found that a
|
|
229
233
|
* repository the resolver had nothing to install in (no `package.json`) cloned
|
|
230
234
|
* perfectly, reported itself correctly, and could never be worked in, because
|
|
231
|
-
* the one path its
|
|
235
|
+
* the one path its sub-agents needed was written on a branch that never ran.
|
|
232
236
|
*
|
|
233
237
|
* Awaited, so it can record intent durably, but it must **return quickly**:
|
|
234
238
|
* it runs inside `repo_clone`, which runs inside a model turn. A host that
|
|
@@ -265,6 +269,50 @@ export interface RepoConfig {
|
|
|
265
269
|
owner: string;
|
|
266
270
|
repo: string;
|
|
267
271
|
}) => void;
|
|
272
|
+
/**
|
|
273
|
+
* The worktrees a host keeps for its sub-agents' work, and the way between
|
|
274
|
+
* them.
|
|
275
|
+
*
|
|
276
|
+
* Set, the plugin offers `repo_worktrees` and `repo_worktree`. A switch moves
|
|
277
|
+
* the installing agent's own tools, so a host sets this on the parent's
|
|
278
|
+
* install and never on a sub-agent's. See {@link RepoWorktrees}.
|
|
279
|
+
*/
|
|
280
|
+
worktrees?: RepoWorktrees;
|
|
281
|
+
/**
|
|
282
|
+
* The tools this install offers, and with them the lines of context that
|
|
283
|
+
* describe them. Unset, every tool. A selection rather than core's
|
|
284
|
+
* `restrictTools`, which drops a plugin's context whole: an agent that
|
|
285
|
+
* coordinates others' work reads about the tools it has and no others.
|
|
286
|
+
*/
|
|
287
|
+
tools?: readonly RepoToolName[];
|
|
288
|
+
}
|
|
289
|
+
/** Every tool and action this plugin can offer, by the name the model sees. */
|
|
290
|
+
export declare const REPO_TOOL_NAMES: readonly ["repo_clone", "repo_fetch", "repo_status", "repo_diff", "repo_commit", "repo_push", "repo_open_pr", "repo_issue_view", "repo_pr_view", "repo_pr_comment", "repo_pr_review_status", "repo_pr_threads", "repo_pr_thread_reply", "repo_pr_checks", "repo_worktrees", "repo_worktree"];
|
|
291
|
+
export type RepoToolName = (typeof REPO_TOOL_NAMES)[number];
|
|
292
|
+
/**
|
|
293
|
+
* Checkouts a host keeps apart from the main one — each holding a branch its
|
|
294
|
+
* writing sub-agents committed on — and the switch that points every repo tool
|
|
295
|
+
* at one of them.
|
|
296
|
+
*
|
|
297
|
+
* The host renders every answer: which worktrees exist, where their checkouts
|
|
298
|
+
* sit and what is safe to do in them are its facts, and a sentence is what the
|
|
299
|
+
* model reads.
|
|
300
|
+
*/
|
|
301
|
+
export interface RepoWorktrees {
|
|
302
|
+
/** Every worktree, with its branch and state. */
|
|
303
|
+
list(): Promise<string>;
|
|
304
|
+
/**
|
|
305
|
+
* Point every repo tool — and whatever else the host routes the same way — at
|
|
306
|
+
* the worktree holding `branch`, or back at the main checkout when it is absent.
|
|
307
|
+
* Answers what the model now has in front of it.
|
|
308
|
+
*
|
|
309
|
+
* The switch holds until the next one. This answer is what the model
|
|
310
|
+
* remembers, so a host that moves the tools on its own says so where the
|
|
311
|
+
* model reads it every turn.
|
|
312
|
+
*/
|
|
313
|
+
use(branch?: string): Promise<string>;
|
|
314
|
+
/** Give up `branch`'s worktree, and the unpushed work in it. */
|
|
315
|
+
release(branch: string): Promise<string>;
|
|
268
316
|
}
|
|
269
317
|
/** What {@link RepoConfig.afterCheckout} is told about a checkout. */
|
|
270
318
|
export interface RepoCheckout {
|
|
@@ -294,9 +342,6 @@ export interface RepoCheckout {
|
|
|
294
342
|
/** True for a first clone, false when an existing tree was refreshed. */
|
|
295
343
|
fresh: boolean;
|
|
296
344
|
}
|
|
297
|
-
export declare function buildRepoTools(config: RepoConfig,
|
|
298
|
-
/** Forwarded to every `exec`; see {@link RepoExec}'s `runtime` option. */
|
|
299
|
-
runtime?: unknown): ToolSet;
|
|
300
345
|
/**
|
|
301
346
|
* No tool here is held for a person's approval — see this plugin's README for
|
|
302
347
|
* why, and for what a fork that wants one does instead.
|