@dynamicagents/plugins 0.13.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 +4 -0
- package/dist/claude-code/cards.js +22 -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 +328 -304
- 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/claude-code/verified.d.ts +1 -1
- package/dist/claude-code/verified.js +1 -1
- 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} +10 -1
- package/dist/{workspace/install.js → sandbox/install-plan.js} +22 -4
- 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 +26 -22
- 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 -474
- package/dist/claude-code/run.js +0 -901
- package/dist/claude-code/session.d.ts +0 -131
- package/dist/claude-code/session.js +0 -285
- 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 -54
- package/dist/workspace/container-deps.js +0 -224
- package/dist/workspace/container-fault.d.ts +0 -38
- package/dist/workspace/container-fault.js +0 -73
- 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 -49
- package/dist/workspace/index.js +0 -58
- package/dist/workspace/install-job.d.ts +0 -121
- package/dist/workspace/install-job.js +0 -866
- package/dist/workspace/object.d.ts +0 -502
- package/dist/workspace/object.js +0 -1427
- 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 -19
- 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
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { truncateOutput } from "@dynamicagents/core";
|
|
2
|
+
import { githubMcp } from "./mcp.js";
|
|
3
|
+
import { DEFAULT_ALLOWED_HOSTS, GITHUB_HOST, parseRepo, repoLocation } from "./url.js";
|
|
2
4
|
/**
|
|
3
5
|
* The machinery every repo tool closes over: the resolved config, the git
|
|
4
6
|
* runners, and the GitHub client.
|
|
5
7
|
*/
|
|
6
8
|
const DEFAULT_WORKDIR = "/workspace";
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* GitHub's API. Not a host's to configure: the hosted MCP server these tools
|
|
11
|
+
* read through is github.com's alone, so an Enterprise base here would point
|
|
12
|
+
* half of this plugin at an install the other half cannot reach. That is
|
|
13
|
+
* {@link GITHUB_HOST}, which `githubRepo` holds every checkout to.
|
|
14
|
+
*/
|
|
15
|
+
const API_BASE = `https://api.${GITHUB_HOST}`;
|
|
8
16
|
const DEFAULT_AUTHOR = {
|
|
9
17
|
name: "da-coder",
|
|
10
18
|
email: "coder@dynamicagents.invalid"
|
|
@@ -30,70 +38,25 @@ const GITHUB_TIMEOUT_MS = 30_000;
|
|
|
30
38
|
*/
|
|
31
39
|
export const GITHUB_PAGE_SIZE = 100;
|
|
32
40
|
/**
|
|
33
|
-
* Where GraphQL lives,
|
|
34
|
-
*
|
|
35
|
-
* On github.com they share an origin and `/graphql` hangs off it. On GitHub
|
|
36
|
-
* Enterprise — which {@link RepoConfig.apiBase} exists for — REST is under
|
|
37
|
-
* `/api/v3` and GraphQL is its **sibling** at `/api/graphql`, not a path beneath
|
|
38
|
-
* it. Appending blindly yields `/api/v3/graphql`, which is a 404 on every
|
|
39
|
-
* Enterprise install and no failure at all on the public API, so the mistake
|
|
40
|
-
* survives every test written against github.com.
|
|
41
|
+
* Where GraphQL lives. A constant, because github.com is the only host these
|
|
42
|
+
* tools talk to — see `./mcp.ts` for why Enterprise is not supported.
|
|
41
43
|
*/
|
|
42
|
-
|
|
43
|
-
const trimmed = apiBase.replace(/\/+$/, "");
|
|
44
|
-
const enterprise = trimmed.replace(/\/api\/v3$/, "/api/graphql");
|
|
45
|
-
return enterprise === trimmed ? `${trimmed}/graphql` : enterprise;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Middle-out truncation, so both the head of a diff and its tail survive.
|
|
49
|
-
*
|
|
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
|
-
* reaches a sibling's directory, and its own advice for a shared helper is to
|
|
53
|
-
* duplicate it — installing `repo` must not drag `/workspace` (and therefore
|
|
54
|
-
* `@cloudflare/computer`) into a consumer's bundle. Fifteen lines is a cheaper
|
|
55
|
-
* price than that coupling, and it is the same reason `exec` is injected here
|
|
56
|
-
* rather than imported.
|
|
57
|
-
*
|
|
58
|
-
* The `half < 1` guard is not padding: without it a small `max` makes `half`
|
|
59
|
-
* zero or negative, and `slice(-0)` is `slice(0)` — the whole string — so the
|
|
60
|
-
* function would return *more* than it was given.
|
|
61
|
-
*/
|
|
62
|
-
export function truncateOutput(text, max) {
|
|
63
|
-
if (text.length <= max)
|
|
64
|
-
return text;
|
|
65
|
-
const marker = (dropped) => `\n\n… [${dropped} characters omitted from the middle] …\n\n`;
|
|
66
|
-
const half = Math.floor((max - marker(text.length).length) / 2);
|
|
67
|
-
if (half < 1)
|
|
68
|
-
return text.slice(0, Math.max(0, max));
|
|
69
|
-
return (text.slice(0, half) + marker(text.length - half * 2) + text.slice(-half));
|
|
70
|
-
}
|
|
44
|
+
const GRAPHQL_URL = `${API_BASE}/graphql`;
|
|
71
45
|
/**
|
|
72
46
|
* What to tell the model when the plumbing, not the command, is the problem.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* subpath whose module graph reaches a sibling's, and installing `repo` must
|
|
77
|
-
* not drag `/computer` in behind it. The wording differs anyway, and the
|
|
78
|
-
* difference is the point — that plugin cannot know the lost command was git,
|
|
79
|
-
* and this one does. What a model needs after a lost `git push` is not "re-run
|
|
80
|
-
* it" but whether re-running it is *safe*.
|
|
47
|
+
* What a model needs after a lost `git push` is not "re-run it" but whether
|
|
48
|
+
* re-running it is *safe*, which only this plugin knows: the lost command was
|
|
49
|
+
* git.
|
|
81
50
|
*/
|
|
82
51
|
function unreachableNote(err) {
|
|
83
|
-
if (err?.code === "EEXEC_LOST") {
|
|
84
|
-
return ("the container was replaced while this command was running, so nothing here " +
|
|
85
|
-
"is known to have finished. This is infrastructure — not git, and not what " +
|
|
86
|
-
"you asked for. The checkout is durable and is exactly as you left it. " +
|
|
87
|
-
"Retry, and read what the retry says rather than assuming it starts from " +
|
|
88
|
-
"nothing: a command that had already reached GitHub may have taken " +
|
|
89
|
-
"effect. Retrying is safe either way — these commands push one branch to " +
|
|
90
|
-
"the branch of the same name, and never force.");
|
|
91
|
-
}
|
|
92
52
|
return (`the command could not be run: ${String(err)}. This is the plumbing — the ` +
|
|
93
53
|
`container, the transport to it, or the host's own configuration — rather ` +
|
|
94
54
|
`than git or anything you passed, and nothing is known to have completed. ` +
|
|
95
|
-
`Retry once
|
|
96
|
-
`
|
|
55
|
+
`Retry once, and read what the retry says rather than assuming it starts ` +
|
|
56
|
+
`from nothing: a command that had already reached GitHub may have taken ` +
|
|
57
|
+
`effect. Retrying is safe either way — these commands push one branch to ` +
|
|
58
|
+
`the branch of the same name, and never force. If it happens again, say so ` +
|
|
59
|
+
`in your result rather than working around it.`);
|
|
97
60
|
}
|
|
98
61
|
/**
|
|
99
62
|
* One git command at a time, per plugin instance.
|
|
@@ -185,13 +148,6 @@ export function actionTask(ctx) {
|
|
|
185
148
|
"the repo plugin's actions run only in turns core started for a task");
|
|
186
149
|
return task;
|
|
187
150
|
}
|
|
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
151
|
export function repoContext(config,
|
|
196
152
|
/**
|
|
197
153
|
* The running sub-agent's `runtime()`, read per command. Forwarded to every
|
|
@@ -199,8 +155,6 @@ export function repoContext(config,
|
|
|
199
155
|
*/
|
|
200
156
|
runtime = () => undefined) {
|
|
201
157
|
const workdir = config.workdir ?? DEFAULT_WORKDIR;
|
|
202
|
-
const apiBase = config.apiBase ?? DEFAULT_API_BASE;
|
|
203
|
-
const graphqlUrl = graphqlEndpoint(apiBase);
|
|
204
158
|
const author = config.author ?? DEFAULT_AUTHOR;
|
|
205
159
|
const allowedHosts = config.allowedHosts ?? DEFAULT_ALLOWED_HOSTS;
|
|
206
160
|
const maxChars = config.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
@@ -412,13 +366,8 @@ runtime = () => undefined) {
|
|
|
412
366
|
* its own sentence to add: a POST whose answer never arrived may have been
|
|
413
367
|
* received, which a GET has no reason to warn about.
|
|
414
368
|
*/
|
|
415
|
-
const github = (tool, path, init) => githubAt(tool, `${
|
|
416
|
-
/**
|
|
417
|
-
* The same call, against a URL rather than a path under {@link apiBase}.
|
|
418
|
-
*
|
|
419
|
-
* GraphQL needs it: on Enterprise the REST base carries `/api/v3` and GraphQL
|
|
420
|
-
* does not live under it — see {@link graphqlEndpoint}.
|
|
421
|
-
*/
|
|
369
|
+
const github = (tool, path, init) => githubAt(tool, `${API_BASE}${path}`, init);
|
|
370
|
+
/** The same call, against a URL rather than a path under {@link API_BASE}. */
|
|
422
371
|
const githubAt = async (tool, url, init) => {
|
|
423
372
|
const secret = credential();
|
|
424
373
|
if ("failure" in secret) {
|
|
@@ -464,13 +413,12 @@ runtime = () => undefined) {
|
|
|
464
413
|
* nothing there" and is acted on accordingly. That is the whole reason this
|
|
465
414
|
* wrapper exists rather than each caller posting to `/graphql` itself.
|
|
466
415
|
*
|
|
467
|
-
* What needs it is
|
|
468
|
-
* REST equivalent at all
|
|
469
|
-
*
|
|
470
|
-
* REST — see {@link file://./tools-review.ts REVIEW_REQUESTS_QUERY}.
|
|
416
|
+
* What still needs it is answering a review thread: replying to one and
|
|
417
|
+
* resolving it have no REST equivalent at all. Reading them does not — that
|
|
418
|
+
* goes through `./mcp.ts` with everything else this plugin reads.
|
|
471
419
|
*/
|
|
472
420
|
const githubGraphql = async (tool, query, variables) => {
|
|
473
|
-
const answered = await githubAt(tool,
|
|
421
|
+
const answered = await githubAt(tool, GRAPHQL_URL, {
|
|
474
422
|
method: "POST",
|
|
475
423
|
body: { query, variables }
|
|
476
424
|
});
|
|
@@ -484,7 +432,7 @@ runtime = () => undefined) {
|
|
|
484
432
|
logFailure(tool, { stderr: detail });
|
|
485
433
|
return {
|
|
486
434
|
ok: false,
|
|
487
|
-
message: `${
|
|
435
|
+
message: `${GRAPHQL_URL} refused the query: ${detail.slice(0, 500)}`
|
|
488
436
|
};
|
|
489
437
|
}
|
|
490
438
|
return { ok: true, data: body.data };
|
|
@@ -499,6 +447,13 @@ runtime = () => undefined) {
|
|
|
499
447
|
* the reach this plugin does not want to widen; and an agent asking about
|
|
500
448
|
* "issue 42" almost always means the repository it is working in, so the extra
|
|
501
449
|
* parameter would mostly be an extra way to be wrong.
|
|
450
|
+
*
|
|
451
|
+
* **The host allowlist is not narrow enough on its own.** It bounds cloning,
|
|
452
|
+
* which is generic git and works on any host; every API call here goes to
|
|
453
|
+
* github.com — see {@link GITHUB_HOST}. A widened `allowedHosts` therefore
|
|
454
|
+
* admits an origin whose `owner/repo` names a *different* repository on
|
|
455
|
+
* github.com, and resolving it would read that one and let a write reach it.
|
|
456
|
+
* So the host is checked here rather than discarded.
|
|
502
457
|
*/
|
|
503
458
|
const githubRepo = async (dir) => {
|
|
504
459
|
const { remote, unreachable } = await origin(dir);
|
|
@@ -508,7 +463,13 @@ runtime = () => undefined) {
|
|
|
508
463
|
return {
|
|
509
464
|
refusal: `${dir} has no origin on an allowed host — clone it with repo_clone first`
|
|
510
465
|
};
|
|
511
|
-
|
|
466
|
+
if (remote.host !== GITHUB_HOST)
|
|
467
|
+
return {
|
|
468
|
+
refusal: `${dir}'s origin is on ${remote.host}, and these tools read and write ` +
|
|
469
|
+
`${GITHUB_HOST} alone — an owner/repo from another host would name a ` +
|
|
470
|
+
`different repository there`
|
|
471
|
+
};
|
|
472
|
+
const parsed = parseRepo(remote.url, [GITHUB_HOST]);
|
|
512
473
|
if (!parsed)
|
|
513
474
|
return {
|
|
514
475
|
refusal: `could not parse an owner/repo out of ${dir}'s origin`
|
|
@@ -575,6 +536,10 @@ runtime = () => undefined) {
|
|
|
575
536
|
fetchOrigin,
|
|
576
537
|
pushBranch,
|
|
577
538
|
origin,
|
|
539
|
+
// Keyed on the config, so one plugin instance keeps one MCP connection
|
|
540
|
+
// however many times its tools are rebuilt — the reason the git queue
|
|
541
|
+
// above is keyed the same way.
|
|
542
|
+
mcp: githubMcp(config, credential, logFailure),
|
|
578
543
|
github,
|
|
579
544
|
githubGraphql,
|
|
580
545
|
githubRepo
|
|
@@ -7,13 +7,29 @@ import type { AgentPlugin } from "@dynamicagents/core";
|
|
|
7
7
|
* though it is implemented in `url.ts`.
|
|
8
8
|
*/
|
|
9
9
|
export { parseRepo } from "./url.js";
|
|
10
|
-
export { graphqlEndpoint, truncateOutput } from "./context.js";
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* The hosted MCP server this plugin reads GitHub through, and the allowlist it
|
|
12
|
+
* is held to. Exported so a deployment can state in its own docs what the
|
|
13
|
+
* token is able to reach, and so a spec can assert the list has not grown.
|
|
14
|
+
*
|
|
15
|
+
* Named for the provider rather than for the protocol: the protocol's own
|
|
16
|
+
* surface is `@dynamicagents/core/mcp`'s, and a bare `MCP_TOOLS` here would not
|
|
17
|
+
* say whose policy it is.
|
|
18
|
+
*/
|
|
19
|
+
export { GITHUB_MCP_URL, GITHUB_MCP_TOOLS } from "./mcp.js";
|
|
20
|
+
/**
|
|
21
|
+
* Over a `/sandbox` object: the sandbox's egress rule for GitHub, and the
|
|
22
|
+
* `RepoGit` that runs the container's own git behind it.
|
|
23
|
+
*/
|
|
24
|
+
export { GITHUB_TOKEN_PLACEHOLDER, githubSandboxEgress, type GitHubSandboxEgressOptions, type GitPushPolicy } from "./sandbox-egress.js";
|
|
25
|
+
export { sandboxRepoGit, type SandboxGitHost } from "./sandbox-git.js";
|
|
26
|
+
/**
|
|
27
|
+
* `@dynamicagents/plugins/repo-github` — clone, commit, push, open a pull
|
|
28
|
+
* request, on GitHub.
|
|
13
29
|
*
|
|
14
30
|
* Layered over a container rather than owning one: it needs a shell with `git`
|
|
15
|
-
* on it, and `@dynamicagents/plugins/
|
|
16
|
-
* `
|
|
31
|
+
* on it, and `@dynamicagents/plugins/sandbox` provides exactly that through
|
|
32
|
+
* `sandboxExec`. Passing `exec` in rather than importing that plugin keeps the
|
|
17
33
|
* two independent — a host with its own container can use this against that
|
|
18
34
|
* instead, and the tests here need no container at all.
|
|
19
35
|
*
|
|
@@ -102,10 +118,10 @@ export type RepoExec = (command: string, options?: {
|
|
|
102
118
|
* `commit` and `checkout` are local, need nothing to authenticate with, and stay
|
|
103
119
|
* on `exec` where they are cheap and where the model can see them work.
|
|
104
120
|
*
|
|
105
|
-
* A host implements this against whatever git it has
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
121
|
+
* A host implements this against whatever git it has. Over `/sandbox`,
|
|
122
|
+
* `sandboxRepoGit` runs the container's own git with a placeholder, and the
|
|
123
|
+
* egress swaps the token in on the Worker side, so the container never holds
|
|
124
|
+
* one.
|
|
109
125
|
*
|
|
110
126
|
* ## Failures arrive as values
|
|
111
127
|
*
|
|
@@ -186,8 +202,6 @@ export interface RepoConfig {
|
|
|
186
202
|
token: () => string;
|
|
187
203
|
/** Where clones land. Defaults to `/workspace`. */
|
|
188
204
|
workdir?: string;
|
|
189
|
-
/** API origin, for GitHub Enterprise. Defaults to the public API. */
|
|
190
|
-
apiBase?: string;
|
|
191
205
|
/** Committer identity. Defaults to a generic agent identity. */
|
|
192
206
|
author?: {
|
|
193
207
|
name: string;
|
|
@@ -197,8 +211,11 @@ export interface RepoConfig {
|
|
|
197
211
|
* Hosts a clone may target, lowercase and exact. Defaults to `github.com`.
|
|
198
212
|
*
|
|
199
213
|
* A fail-closed allowlist rather than a blocklist, because the failure it
|
|
200
|
-
* prevents is credential exfiltration and the input is model-chosen.
|
|
201
|
-
*
|
|
214
|
+
* prevents is credential exfiltration and the input is model-chosen. An empty
|
|
215
|
+
* array clones nothing.
|
|
216
|
+
*
|
|
217
|
+
* This bounds *cloning*, which is generic git and works against any host. The
|
|
218
|
+
* GitHub tools are github.com's alone — see `./mcp.ts`.
|
|
202
219
|
*/
|
|
203
220
|
allowedHosts?: string[];
|
|
204
221
|
/**
|
|
@@ -287,7 +304,7 @@ export interface RepoConfig {
|
|
|
287
304
|
tools?: readonly RepoToolName[];
|
|
288
305
|
}
|
|
289
306
|
/** 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", "
|
|
307
|
+
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_threads", "repo_pr_thread_reply", "repo_pr_checks", "repo_worktrees", "repo_worktree"];
|
|
291
308
|
export type RepoToolName = (typeof REPO_TOOL_NAMES)[number];
|
|
292
309
|
/**
|
|
293
310
|
* Checkouts a host keeps apart from the main one — each holding a branch its
|
|
@@ -14,7 +14,22 @@ import { worktreesTools } from "./tools-worktrees.js";
|
|
|
14
14
|
* though it is implemented in `url.ts`.
|
|
15
15
|
*/
|
|
16
16
|
export { parseRepo } from "./url.js";
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The hosted MCP server this plugin reads GitHub through, and the allowlist it
|
|
19
|
+
* is held to. Exported so a deployment can state in its own docs what the
|
|
20
|
+
* token is able to reach, and so a spec can assert the list has not grown.
|
|
21
|
+
*
|
|
22
|
+
* Named for the provider rather than for the protocol: the protocol's own
|
|
23
|
+
* surface is `@dynamicagents/core/mcp`'s, and a bare `MCP_TOOLS` here would not
|
|
24
|
+
* say whose policy it is.
|
|
25
|
+
*/
|
|
26
|
+
export { GITHUB_MCP_URL, GITHUB_MCP_TOOLS } from "./mcp.js";
|
|
27
|
+
/**
|
|
28
|
+
* Over a `/sandbox` object: the sandbox's egress rule for GitHub, and the
|
|
29
|
+
* `RepoGit` that runs the container's own git behind it.
|
|
30
|
+
*/
|
|
31
|
+
export { GITHUB_TOKEN_PLACEHOLDER, githubSandboxEgress } from "./sandbox-egress.js";
|
|
32
|
+
export { sandboxRepoGit } from "./sandbox-git.js";
|
|
18
33
|
/** Every tool and action this plugin can offer, by the name the model sees. */
|
|
19
34
|
export const REPO_TOOL_NAMES = [
|
|
20
35
|
"repo_clone",
|
|
@@ -27,7 +42,6 @@ export const REPO_TOOL_NAMES = [
|
|
|
27
42
|
"repo_issue_view",
|
|
28
43
|
"repo_pr_view",
|
|
29
44
|
"repo_pr_comment",
|
|
30
|
-
"repo_pr_review_status",
|
|
31
45
|
"repo_pr_threads",
|
|
32
46
|
"repo_pr_thread_reply",
|
|
33
47
|
"repo_pr_checks",
|
|
@@ -46,7 +60,7 @@ export function repo(config) {
|
|
|
46
60
|
has("repo_issue_view") &&
|
|
47
61
|
"`repo_issue_view` reads an issue or pull request with its comments",
|
|
48
62
|
has("repo_pr_view") &&
|
|
49
|
-
"`repo_pr_view` shows a pull request's state and the files it touches",
|
|
63
|
+
"`repo_pr_view` shows a pull request's state, the reviews that have landed on it and the files it touches",
|
|
50
64
|
has("repo_pr_comment") && "`repo_pr_comment` leaves a comment"
|
|
51
65
|
].filter((line) => typeof line === "string");
|
|
52
66
|
const worktrees = config.worktrees && [
|
|
@@ -82,8 +96,9 @@ export function repo(config) {
|
|
|
82
96
|
"`repo_open_pr` opens the pull request and returns its URL.",
|
|
83
97
|
views.length > 0 &&
|
|
84
98
|
`${views.join(", ")}. ${views.length > 1 ? "Each acts" : "It acts"} on the repository you have checked out — read the issue a task refers to before guessing what it asks for.`,
|
|
85
|
-
has("
|
|
86
|
-
"
|
|
99
|
+
has("repo_pr_threads") &&
|
|
100
|
+
has("repo_pr_view") &&
|
|
101
|
+
"Read `repo_pr_view` before reading a review: a review that has not landed has left nothing, so an empty list of threads means nothing yet rather than nothing to do.",
|
|
87
102
|
has("repo_pr_threads") &&
|
|
88
103
|
"`repo_pr_threads` reads the review threads — the comments left on particular lines, which are not on the " +
|
|
89
104
|
(has("repo_issue_view")
|
|
@@ -110,7 +125,11 @@ export function repo(config) {
|
|
|
110
125
|
...(rules.length > 0 ? [rules.join(" ")] : [])
|
|
111
126
|
].join("\n");
|
|
112
127
|
return definePlugin({
|
|
113
|
-
|
|
128
|
+
// The installed capability's identity, and so the label core namespaces
|
|
129
|
+
// this plugin's context block under. The provider is in it for the same
|
|
130
|
+
// reason it is in the subpath: an agent installs one forge plugin, and
|
|
131
|
+
// which one is a thing the model may as well be able to read.
|
|
132
|
+
name: "repo-github",
|
|
114
133
|
// `runtime` goes through to `exec` untouched and is read per command, so a
|
|
115
134
|
// sub-agent's git commands run in the same container its parent cloned
|
|
116
135
|
// into.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub's hosted MCP server, called from the Worker.
|
|
3
|
+
*
|
|
4
|
+
* The reads this plugin makes — a pull request, its files, its reviews, its
|
|
5
|
+
* review threads, its checks, an issue and its comments — are one
|
|
6
|
+
* vendor-maintained surface. What stays ours is everything above the transport:
|
|
7
|
+
* the tool names, their schemas, the repository binding and the renders. The
|
|
8
|
+
* model never sees an MCP tool; see `./index.ts` for why the tools are wrapped
|
|
9
|
+
* rather than surfaced.
|
|
10
|
+
*
|
|
11
|
+
* The transport itself is not here and not ours. `@dynamicagents/core/mcp`
|
|
12
|
+
* holds it, over the official `@modelcontextprotocol/client`, and its module
|
|
13
|
+
* comment is where the protocol decisions live — why no `authProvider` is
|
|
14
|
+
* configured, which headers a caller may not set, how a connection is kept per
|
|
15
|
+
* plugin instance. What this file is, is the policy that is GitHub's.
|
|
16
|
+
*
|
|
17
|
+
* ## The credential, and what bounds it
|
|
18
|
+
*
|
|
19
|
+
* The token is the host's, read through {@link RepoConfig.token} at the moment
|
|
20
|
+
* of use and sent as `Authorization: Bearer` from here — the Worker. It reaches
|
|
21
|
+
* no container: this host is deliberately absent from the egress gateway's
|
|
22
|
+
* `GITHUB_HOSTS`, so nothing inside a session's container can offer the
|
|
23
|
+
* placeholder to it and be given the real one.
|
|
24
|
+
*
|
|
25
|
+
* Two caps ride on every request, and they are policy rather than tuning:
|
|
26
|
+
*
|
|
27
|
+
* 1. {@link GITHUB_MCP_TOOLS} goes out as `X-MCP-Tools`, an **enable** list.
|
|
28
|
+
* The server offers nothing else, so a bug here — or an argument a prompt
|
|
29
|
+
* injection talked the model into — cannot reach a tool that writes.
|
|
30
|
+
* 2. {@link GITHUB_MCP_URL} is the server's read-only variant, which refuses a
|
|
31
|
+
* write whatever the list says.
|
|
32
|
+
*
|
|
33
|
+
* Both, not either. The list is the narrow bound and the path is the one that
|
|
34
|
+
* still holds if the list is ever edited carelessly. Neither is redundant with
|
|
35
|
+
* the wrapping: wrapping keeps GitHub's schemas out of the model's context, and
|
|
36
|
+
* these two bound what this Worker can do with the token at all.
|
|
37
|
+
*
|
|
38
|
+
* GitHub Enterprise Server is not supported, which is why the URL is a constant
|
|
39
|
+
* rather than something a host configures.
|
|
40
|
+
*/
|
|
41
|
+
import { type McpAnswer } from "@dynamicagents/core/mcp";
|
|
42
|
+
import type { RepoConfig } from "./index.js";
|
|
43
|
+
/**
|
|
44
|
+
* The server, read-only. A single toolset or a modifier is a path segment on
|
|
45
|
+
* this server — see the `X-MCP-Tools` note above for why the list does the
|
|
46
|
+
* narrow work and this does the coarse.
|
|
47
|
+
*/
|
|
48
|
+
export declare const GITHUB_MCP_URL = "https://api.githubcopilot.com/mcp/readonly";
|
|
49
|
+
/**
|
|
50
|
+
* Every tool this plugin may call, as `X-MCP-Tools`.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately not configurable. A host that could widen this could hand the
|
|
53
|
+
* deployment's token to a tool nobody reviewed, and the point of an allowlist
|
|
54
|
+
* is that adding to it is a code change somebody reads.
|
|
55
|
+
*
|
|
56
|
+
* Both are read tools. `pull_request_read` is one name over nine methods —
|
|
57
|
+
* `get`, `get_files`, `get_reviews`, `get_review_comments`, `get_check_runs`,
|
|
58
|
+
* `get_status` are the ones used here — and `issue_read` covers an issue and
|
|
59
|
+
* its comments.
|
|
60
|
+
*
|
|
61
|
+
* **`pull_request_review_write` is the one deliberate absence worth naming.**
|
|
62
|
+
* It is the only tool that resolves a review thread, and the same name submits
|
|
63
|
+
* a review — `APPROVE` included — so taking thread resolution would mean taking
|
|
64
|
+
* self-approval. Nothing needs it: a revising Claude Code session answers and
|
|
65
|
+
* resolves threads itself, with `gh` inside its container. Merging, commenting
|
|
66
|
+
* and replying are absent for the same reason.
|
|
67
|
+
*/
|
|
68
|
+
export declare const GITHUB_MCP_TOOLS: readonly ["pull_request_read", "issue_read"];
|
|
69
|
+
/**
|
|
70
|
+
* One call to GitHub's MCP server, as the rest of this plugin asks for it.
|
|
71
|
+
*
|
|
72
|
+
* `tool` is this plugin's own tool — `repo_pr_view` — and is what the failure
|
|
73
|
+
* log names, because that is the name an operator reading the log has in front
|
|
74
|
+
* of them. `name` is the MCP tool, which must be one of
|
|
75
|
+
* {@link GITHUB_MCP_TOOLS}.
|
|
76
|
+
*/
|
|
77
|
+
export type GithubMcpCall = (tool: string, name: string, args: Record<string, unknown>) => Promise<McpAnswer>;
|
|
78
|
+
/**
|
|
79
|
+
* The caller for one plugin instance.
|
|
80
|
+
*
|
|
81
|
+
* `config` is the key a connection is kept against, the way the git queue
|
|
82
|
+
* beside it is keyed: the tools are rebuilt every turn, so anything held in
|
|
83
|
+
* that closure would renegotiate on each one.
|
|
84
|
+
*
|
|
85
|
+
* No turn signal is threaded through. Core's client takes one, and a caller
|
|
86
|
+
* that has one should pass it; these tools do not hold the turn's signal today,
|
|
87
|
+
* and the call is bounded by core's own deadline either way.
|
|
88
|
+
*/
|
|
89
|
+
export declare function githubMcp(config: RepoConfig, credential: () => {
|
|
90
|
+
token: string;
|
|
91
|
+
} | {
|
|
92
|
+
failure: string;
|
|
93
|
+
}, logFailure: (tool: string, detail: {
|
|
94
|
+
stderr?: string;
|
|
95
|
+
}) => void): GithubMcpCall;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub's hosted MCP server, called from the Worker.
|
|
3
|
+
*
|
|
4
|
+
* The reads this plugin makes — a pull request, its files, its reviews, its
|
|
5
|
+
* review threads, its checks, an issue and its comments — are one
|
|
6
|
+
* vendor-maintained surface. What stays ours is everything above the transport:
|
|
7
|
+
* the tool names, their schemas, the repository binding and the renders. The
|
|
8
|
+
* model never sees an MCP tool; see `./index.ts` for why the tools are wrapped
|
|
9
|
+
* rather than surfaced.
|
|
10
|
+
*
|
|
11
|
+
* The transport itself is not here and not ours. `@dynamicagents/core/mcp`
|
|
12
|
+
* holds it, over the official `@modelcontextprotocol/client`, and its module
|
|
13
|
+
* comment is where the protocol decisions live — why no `authProvider` is
|
|
14
|
+
* configured, which headers a caller may not set, how a connection is kept per
|
|
15
|
+
* plugin instance. What this file is, is the policy that is GitHub's.
|
|
16
|
+
*
|
|
17
|
+
* ## The credential, and what bounds it
|
|
18
|
+
*
|
|
19
|
+
* The token is the host's, read through {@link RepoConfig.token} at the moment
|
|
20
|
+
* of use and sent as `Authorization: Bearer` from here — the Worker. It reaches
|
|
21
|
+
* no container: this host is deliberately absent from the egress gateway's
|
|
22
|
+
* `GITHUB_HOSTS`, so nothing inside a session's container can offer the
|
|
23
|
+
* placeholder to it and be given the real one.
|
|
24
|
+
*
|
|
25
|
+
* Two caps ride on every request, and they are policy rather than tuning:
|
|
26
|
+
*
|
|
27
|
+
* 1. {@link GITHUB_MCP_TOOLS} goes out as `X-MCP-Tools`, an **enable** list.
|
|
28
|
+
* The server offers nothing else, so a bug here — or an argument a prompt
|
|
29
|
+
* injection talked the model into — cannot reach a tool that writes.
|
|
30
|
+
* 2. {@link GITHUB_MCP_URL} is the server's read-only variant, which refuses a
|
|
31
|
+
* write whatever the list says.
|
|
32
|
+
*
|
|
33
|
+
* Both, not either. The list is the narrow bound and the path is the one that
|
|
34
|
+
* still holds if the list is ever edited carelessly. Neither is redundant with
|
|
35
|
+
* the wrapping: wrapping keeps GitHub's schemas out of the model's context, and
|
|
36
|
+
* these two bound what this Worker can do with the token at all.
|
|
37
|
+
*
|
|
38
|
+
* GitHub Enterprise Server is not supported, which is why the URL is a constant
|
|
39
|
+
* rather than something a host configures.
|
|
40
|
+
*/
|
|
41
|
+
import { mcpClient } from "@dynamicagents/core/mcp";
|
|
42
|
+
/**
|
|
43
|
+
* The server, read-only. A single toolset or a modifier is a path segment on
|
|
44
|
+
* this server — see the `X-MCP-Tools` note above for why the list does the
|
|
45
|
+
* narrow work and this does the coarse.
|
|
46
|
+
*/
|
|
47
|
+
export const GITHUB_MCP_URL = "https://api.githubcopilot.com/mcp/readonly";
|
|
48
|
+
/**
|
|
49
|
+
* Every tool this plugin may call, as `X-MCP-Tools`.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately not configurable. A host that could widen this could hand the
|
|
52
|
+
* deployment's token to a tool nobody reviewed, and the point of an allowlist
|
|
53
|
+
* is that adding to it is a code change somebody reads.
|
|
54
|
+
*
|
|
55
|
+
* Both are read tools. `pull_request_read` is one name over nine methods —
|
|
56
|
+
* `get`, `get_files`, `get_reviews`, `get_review_comments`, `get_check_runs`,
|
|
57
|
+
* `get_status` are the ones used here — and `issue_read` covers an issue and
|
|
58
|
+
* its comments.
|
|
59
|
+
*
|
|
60
|
+
* **`pull_request_review_write` is the one deliberate absence worth naming.**
|
|
61
|
+
* It is the only tool that resolves a review thread, and the same name submits
|
|
62
|
+
* a review — `APPROVE` included — so taking thread resolution would mean taking
|
|
63
|
+
* self-approval. Nothing needs it: a revising Claude Code session answers and
|
|
64
|
+
* resolves threads itself, with `gh` inside its container. Merging, commenting
|
|
65
|
+
* and replying are absent for the same reason.
|
|
66
|
+
*/
|
|
67
|
+
export const GITHUB_MCP_TOOLS = ["pull_request_read", "issue_read"];
|
|
68
|
+
/**
|
|
69
|
+
* The caller for one plugin instance.
|
|
70
|
+
*
|
|
71
|
+
* `config` is the key a connection is kept against, the way the git queue
|
|
72
|
+
* beside it is keyed: the tools are rebuilt every turn, so anything held in
|
|
73
|
+
* that closure would renegotiate on each one.
|
|
74
|
+
*
|
|
75
|
+
* No turn signal is threaded through. Core's client takes one, and a caller
|
|
76
|
+
* that has one should pass it; these tools do not hold the turn's signal today,
|
|
77
|
+
* and the call is bounded by core's own deadline either way.
|
|
78
|
+
*/
|
|
79
|
+
export function githubMcp(config, credential, logFailure) {
|
|
80
|
+
const call = mcpClient({
|
|
81
|
+
url: GITHUB_MCP_URL,
|
|
82
|
+
keyedOn: config,
|
|
83
|
+
// Thrown rather than returned, because core keeps an `Error`'s own message
|
|
84
|
+
// verbatim: the sentence a host's unreadable secret deserves is written
|
|
85
|
+
// once, in `repoContext`, and reaches the model unframed.
|
|
86
|
+
token: () => {
|
|
87
|
+
const secret = credential();
|
|
88
|
+
if ("failure" in secret)
|
|
89
|
+
throw new Error(secret.failure);
|
|
90
|
+
return secret.token;
|
|
91
|
+
},
|
|
92
|
+
headers: () => ({
|
|
93
|
+
"x-mcp-tools": GITHUB_MCP_TOOLS.join(","),
|
|
94
|
+
"user-agent": "da-coder"
|
|
95
|
+
}),
|
|
96
|
+
clientInfo: { name: "dynamicagents-repo-github", version: "1" }
|
|
97
|
+
});
|
|
98
|
+
return async (tool, name, args) => {
|
|
99
|
+
const answered = await call(name, args);
|
|
100
|
+
// Said out loud as well as returned. A failure that only reaches the model
|
|
101
|
+
// leaves nothing behind for an operator, and an unreadable credential is a
|
|
102
|
+
// likely reason to be on this path at all.
|
|
103
|
+
if (!answered.ok)
|
|
104
|
+
logFailure(tool, { stderr: answered.message });
|
|
105
|
+
return answered;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type SandboxEgressRule } from "../sandbox/egress.js";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub from inside a sandbox, with the token on the Worker side: the
|
|
4
|
+
* sandbox's egress rule for it.
|
|
5
|
+
*
|
|
6
|
+
* The harness intercepts `github.com` over HTTPS, and git's credential helper
|
|
7
|
+
* answers with {@link GITHUB_TOKEN_PLACEHOLDER}. **Never an `insteadOf` onto a
|
|
8
|
+
* placeholder host:** git reports the rewritten URL to whatever reads a remote
|
|
9
|
+
* (`git remote get-url`, `git remote -v`), so `/repo` would take a checkout's
|
|
10
|
+
* origin for a host it does not allow, and `gh` would find no GitHub remote. A
|
|
11
|
+
* sandbox allowed the API intercepts `api.github.com` too, and gives `gh` the
|
|
12
|
+
* placeholder as `GH_TOKEN`.
|
|
13
|
+
*
|
|
14
|
+
* The egress swaps the placeholder for the real token, so a process can *use*
|
|
15
|
+
* GitHub as the deployment's account and never *read* the token. A request
|
|
16
|
+
* that presents no placeholder goes out anonymous, as it would anywhere else.
|
|
17
|
+
*
|
|
18
|
+
* Any process in the container can present the placeholder, a model's own
|
|
19
|
+
* shell included, so what `repo_push` refuses is refused here too, for every
|
|
20
|
+
* credentialed push: a ref that is not a branch, a protected name, and the
|
|
21
|
+
* repository's own default branch, which work reaches through a pull request.
|
|
22
|
+
*/
|
|
23
|
+
/** What `GH_TOKEN` and git's credential helper hold. Shaped like a classic token, so nothing refuses it on shape. */
|
|
24
|
+
export declare const GITHUB_TOKEN_PLACEHOLDER: string;
|
|
25
|
+
/**
|
|
26
|
+
* Where a credentialed git request may go, beyond what every push is held to.
|
|
27
|
+
*
|
|
28
|
+
* Checked on git's own requests only: anonymous traffic to GitHub is whatever
|
|
29
|
+
* GitHub allows anyone. `repos` are `owner/repo`; `branches` are branch names,
|
|
30
|
+
* a trailing `*` matching any suffix. A deletion is an update like any other,
|
|
31
|
+
* so it is held to the same names.
|
|
32
|
+
*/
|
|
33
|
+
export interface GitPushPolicy {
|
|
34
|
+
readonly repos: readonly string[];
|
|
35
|
+
readonly branches: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface GitHubSandboxEgressOptions {
|
|
38
|
+
/** The `env` binding holding the token, read at the moment it is used and never carried. */
|
|
39
|
+
tokenBinding: string;
|
|
40
|
+
/** Absent: any repository and any work branch the token reaches. */
|
|
41
|
+
push?: GitPushPolicy;
|
|
42
|
+
/** Intercept `api.github.com` as well, so `gh` works as the deployment's account. */
|
|
43
|
+
api?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The egress rule of a sandbox whose processes use GitHub. */
|
|
46
|
+
export declare function githubSandboxEgress(options: GitHubSandboxEgressOptions): SandboxEgressRule;
|
|
47
|
+
/** `owner/repo` of a smart-HTTP git path, and which service it asks for. */
|
|
48
|
+
export declare function gitTarget(pathname: string): {
|
|
49
|
+
repo: string;
|
|
50
|
+
service: "upload-pack" | "receive-pack" | undefined;
|
|
51
|
+
} | undefined;
|
|
52
|
+
/** Whether `branch` is one the policy allows. */
|
|
53
|
+
export declare function branchAllowed(policy: GitPushPolicy, branch: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The refs a `git-receive-pack` request updates, from its pkt-line commands.
|
|
56
|
+
*
|
|
57
|
+
* Each command is `<old> <new> <ref>`, the first carrying `\0<capabilities>`,
|
|
58
|
+
* and the list ends at a flush packet; the pack follows. Only the list is read,
|
|
59
|
+
* so a caller hands this one branch of a `tee()` and forwards the other: the
|
|
60
|
+
* pack is never held in the Worker. git compresses the request body when it is
|
|
61
|
+
* large, so a gzip body is inflated as it is read.
|
|
62
|
+
*
|
|
63
|
+
* Read as strictly as git reads it, since the refs this returns are the ones a
|
|
64
|
+
* policy allows: a length is four hex digits, `0000` ends the list, and any
|
|
65
|
+
* other packet shorter than its own length or past the ceiling is refused
|
|
66
|
+
* rather than skipped. A ref is UTF-8, as git writes it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function receivePackRefs(body: ReadableStream<Uint8Array>, encoding: string | null): Promise<string[]>;
|