@geminixiang/mikan 1.0.0-beta.20 → 1.0.0-beta.21
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/CHANGELOG.md +34 -0
- package/README.md +18 -11
- package/dist/adapters/buffered-responder.d.ts +60 -0
- package/dist/adapters/buffered-responder.d.ts.map +1 -0
- package/dist/adapters/buffered-responder.js +201 -0
- package/dist/adapters/buffered-responder.js.map +1 -0
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +42 -177
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/github/bot.d.ts +14 -76
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +52 -443
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/github/client.d.ts +4 -0
- package/dist/adapters/github/client.d.ts.map +1 -1
- package/dist/adapters/github/client.js +5 -0
- package/dist/adapters/github/client.js.map +1 -1
- package/dist/adapters/github/context.d.ts.map +1 -1
- package/dist/adapters/github/context.js +32 -104
- package/dist/adapters/github/context.js.map +1 -1
- package/dist/adapters/github/github-ops.d.ts +90 -0
- package/dist/adapters/github/github-ops.d.ts.map +1 -0
- package/dist/adapters/github/github-ops.js +405 -0
- package/dist/adapters/github/github-ops.js.map +1 -0
- package/dist/adapters/github/repo.d.ts +2 -0
- package/dist/adapters/github/repo.d.ts.map +1 -1
- package/dist/adapters/github/repo.js +5 -0
- package/dist/adapters/github/repo.js.map +1 -1
- package/dist/adapters/github/tools/checks.d.ts.map +1 -1
- package/dist/adapters/github/tools/checks.js +6 -17
- package/dist/adapters/github/tools/checks.js.map +1 -1
- package/dist/adapters/github/tools/issue.d.ts.map +1 -1
- package/dist/adapters/github/tools/issue.js +6 -17
- package/dist/adapters/github/tools/issue.js.map +1 -1
- package/dist/adapters/github/tools/pr.d.ts.map +1 -1
- package/dist/adapters/github/tools/pr.js +6 -17
- package/dist/adapters/github/tools/pr.js.map +1 -1
- package/dist/adapters/github/tools/read.d.ts.map +1 -1
- package/dist/adapters/github/tools/read.js +6 -17
- package/dist/adapters/github/tools/read.js.map +1 -1
- package/dist/adapters/github/tools/review-reply.d.ts.map +1 -1
- package/dist/adapters/github/tools/review-reply.js +6 -17
- package/dist/adapters/github/tools/review-reply.js.map +1 -1
- package/dist/adapters/github/tools/sync.d.ts.map +1 -1
- package/dist/adapters/github/tools/sync.js +6 -17
- package/dist/adapters/github/tools/sync.js.map +1 -1
- package/dist/adapters/slack/bot.js +1 -1
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +33 -151
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +0 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +28 -32
- package/dist/agent.js.map +1 -1
- package/dist/cli/arg-grammar.d.ts +25 -0
- package/dist/cli/arg-grammar.d.ts.map +1 -0
- package/dist/cli/arg-grammar.js +59 -0
- package/dist/cli/arg-grammar.js.map +1 -0
- package/dist/cli/boot.d.ts +16 -0
- package/dist/cli/boot.d.ts.map +1 -0
- package/dist/cli/boot.js +119 -0
- package/dist/cli/boot.js.map +1 -0
- package/dist/cli/ext.js +9 -11
- package/dist/cli/ext.js.map +1 -1
- package/dist/commands/extensions.js +2 -2
- package/dist/commands/extensions.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +5 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +8 -8
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +7 -5
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -41
- package/dist/config.js.map +1 -1
- package/dist/env-manifest.d.ts +35 -0
- package/dist/env-manifest.d.ts.map +1 -0
- package/dist/env-manifest.js +225 -0
- package/dist/env-manifest.js.map +1 -0
- package/dist/events.d.ts +0 -10
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +9 -82
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +1 -0
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +33 -36
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/event-format.d.ts +66 -0
- package/dist/harness/event-format.d.ts.map +1 -0
- package/dist/harness/event-format.js +112 -0
- package/dist/harness/event-format.js.map +1 -0
- package/dist/harness/extensions/loader.d.ts +9 -6
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +35 -50
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/extensions/types.d.ts +10 -14
- package/dist/harness/extensions/types.d.ts.map +1 -1
- package/dist/harness/extensions/types.js.map +1 -1
- package/dist/harness/index.d.ts +1 -0
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +1 -0
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/runner.d.ts +16 -0
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +27 -1
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +18 -1
- package/dist/harness/subagent-runner.d.ts.map +1 -1
- package/dist/harness/subagent-runner.js +62 -30
- package/dist/harness/subagent-runner.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.js +69 -121
- package/dist/main.js.map +1 -1
- package/dist/observability/instrument.js +6 -4
- package/dist/observability/instrument.js.map +1 -1
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +3 -2
- package/dist/observability/sentry.js.map +1 -1
- package/dist/runtime/conversation-runtime.js +13 -3
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +4 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts +1 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +4 -1
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts +1 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +4 -1
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/firecracker.d.ts +1 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/firecracker.js +4 -1
- package/dist/sandbox/firecracker.js.map +1 -1
- package/dist/sandbox/gondolin-bootstrap.d.ts +26 -0
- package/dist/sandbox/gondolin-bootstrap.d.ts.map +1 -0
- package/dist/sandbox/gondolin-bootstrap.js +27 -0
- package/dist/sandbox/gondolin-bootstrap.js.map +1 -0
- package/dist/sandbox/gondolin-contract.d.ts +125 -0
- package/dist/sandbox/gondolin-contract.d.ts.map +1 -0
- package/dist/sandbox/gondolin-contract.js +155 -0
- package/dist/sandbox/gondolin-contract.js.map +1 -0
- package/dist/sandbox/gondolin-fleet.d.ts +1 -1
- package/dist/sandbox/gondolin-fleet.d.ts.map +1 -1
- package/dist/sandbox/gondolin-fleet.js +1 -1
- package/dist/sandbox/gondolin-fleet.js.map +1 -1
- package/dist/sandbox/gondolin-gateway.d.ts +1 -0
- package/dist/sandbox/gondolin-gateway.d.ts.map +1 -1
- package/dist/sandbox/gondolin-gateway.js +13 -6
- package/dist/sandbox/gondolin-gateway.js.map +1 -1
- package/dist/sandbox/gondolin-inventory.d.ts +2 -16
- package/dist/sandbox/gondolin-inventory.d.ts.map +1 -1
- package/dist/sandbox/gondolin-inventory.js +2 -12
- package/dist/sandbox/gondolin-inventory.js.map +1 -1
- package/dist/sandbox/gondolin-placement.d.ts +0 -2
- package/dist/sandbox/gondolin-placement.d.ts.map +1 -1
- package/dist/sandbox/gondolin-placement.js +0 -6
- package/dist/sandbox/gondolin-placement.js.map +1 -1
- package/dist/sandbox/gondolin-recovery.d.ts +33 -0
- package/dist/sandbox/gondolin-recovery.d.ts.map +1 -0
- package/dist/sandbox/gondolin-recovery.js +39 -0
- package/dist/sandbox/gondolin-recovery.js.map +1 -0
- package/dist/sandbox/gondolin-remote.d.ts +2 -5
- package/dist/sandbox/gondolin-remote.d.ts.map +1 -1
- package/dist/sandbox/gondolin-remote.js +12 -39
- package/dist/sandbox/gondolin-remote.js.map +1 -1
- package/dist/sandbox/gondolin-worker-client.d.ts +8 -12
- package/dist/sandbox/gondolin-worker-client.d.ts.map +1 -1
- package/dist/sandbox/gondolin-worker-client.js +13 -15
- package/dist/sandbox/gondolin-worker-client.js.map +1 -1
- package/dist/sandbox/gondolin-worker.d.ts +2 -40
- package/dist/sandbox/gondolin-worker.d.ts.map +1 -1
- package/dist/sandbox/gondolin-worker.js.map +1 -1
- package/dist/sandbox/gondolin.d.ts +1 -0
- package/dist/sandbox/gondolin.d.ts.map +1 -1
- package/dist/sandbox/gondolin.js +8 -4
- package/dist/sandbox/gondolin.js.map +1 -1
- package/dist/sandbox/host.d.ts +1 -0
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +3 -0
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/index.d.ts +9 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +14 -0
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +5 -0
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +1 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +5 -2
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts +32 -0
- package/dist/sessions/chat-history-sync.d.ts.map +1 -0
- package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +24 -83
- package/dist/sessions/chat-history-sync.js.map +1 -0
- package/dist/sessions/conversation-log.d.ts +13 -0
- package/dist/sessions/conversation-log.d.ts.map +1 -0
- package/dist/sessions/conversation-log.js +47 -0
- package/dist/sessions/conversation-log.js.map +1 -0
- package/dist/sessions/rotation.d.ts +2 -0
- package/dist/sessions/rotation.d.ts.map +1 -0
- package/dist/sessions/rotation.js +39 -0
- package/dist/sessions/rotation.js.map +1 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +4 -10
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +2 -2
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings-mutation.d.ts +23 -0
- package/dist/settings-mutation.d.ts.map +1 -0
- package/dist/settings-mutation.js +42 -0
- package/dist/settings-mutation.js.map +1 -0
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +27 -46
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/host-fn-tool.d.ts +21 -0
- package/dist/tools/host-fn-tool.d.ts.map +1 -0
- package/dist/tools/host-fn-tool.js +32 -0
- package/dist/tools/host-fn-tool.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/react.d.ts.map +1 -1
- package/dist/tools/react.js +6 -17
- package/dist/tools/react.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +7 -26
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +5 -2
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/subagent-slots.d.ts +28 -0
- package/dist/tools/subagent-slots.d.ts.map +1 -0
- package/dist/tools/subagent-slots.js +57 -0
- package/dist/tools/subagent-slots.js.map +1 -0
- package/dist/tools/subagent.d.ts +7 -2
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +160 -111
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/types.d.ts +10 -9
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/types.d.ts +5 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/date.d.ts +1 -1
- package/dist/utils/date.js +1 -1
- package/dist/utils/date.js.map +1 -1
- package/dist/vault/disabled.d.ts.map +1 -1
- package/dist/vault/disabled.js +0 -1
- package/dist/vault/disabled.js.map +1 -1
- package/dist/vault/index.d.ts +0 -2
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +0 -10
- package/dist/vault/index.js.map +1 -1
- package/dist/vault/injection.d.ts +20 -0
- package/dist/vault/injection.d.ts.map +1 -0
- package/dist/vault/injection.js +27 -0
- package/dist/vault/injection.js.map +1 -0
- package/dist/vault/types.d.ts +0 -2
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/vault/types.js.map +1 -1
- package/dist/web/admin/portal.d.ts +3 -3
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +41 -39
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/types.d.ts +3 -0
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/agent-events/portal.d.ts.map +1 -1
- package/dist/web/agent-events/portal.js +2 -1
- package/dist/web/agent-events/portal.js.map +1 -1
- package/package.json +1 -1
- package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
- package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
- package/dist/sessions/agent-memory-file-manager.js.map +0 -1
- package/dist/vault/routing.d.ts +0 -4
- package/dist/vault/routing.d.ts.map +0 -1
- package/dist/vault/routing.js +0 -6
- package/dist/vault/routing.js.map +0 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import * as log from "../../log.js";
|
|
3
|
+
import { GithubApiError, GITHUB_MAX_COMMENT_LENGTH, githubRetry } from "./client.js";
|
|
4
|
+
import { CLOUD_BUILD_APP_SLUG, CloudBuildLogUnavailableError, fetchCloudBuildLog, isCloudBuildId, projectFromDetailsUrl, } from "./cloudbuild.js";
|
|
5
|
+
import { parseGithubConversationId } from "./ids.js";
|
|
6
|
+
import { conversationRepoDir, GITHUB_PUSH_BRANCH_PATTERN, pushBranch, syncRepo } from "./repo.js";
|
|
7
|
+
/** Both CI log modes tail-truncate to this many characters. */
|
|
8
|
+
const MAX_LOG_CHARS = 20000;
|
|
9
|
+
function truncateLogTail(logText) {
|
|
10
|
+
return logText.length > MAX_LOG_CHARS
|
|
11
|
+
? `…(truncated to the last ${MAX_LOG_CHARS} chars)\n${logText.slice(-MAX_LOG_CHARS)}`
|
|
12
|
+
: logText;
|
|
13
|
+
}
|
|
14
|
+
/** Whether the conversation's issue is a PR, for callers where the triggering
|
|
15
|
+
* item didn't carry that knowledge. Errs toward plain issue. */
|
|
16
|
+
export async function fetchIsPr(client, ref) {
|
|
17
|
+
try {
|
|
18
|
+
const issue = await githubRetry(() => client.getIssue(ref.owner, ref.repo, ref.number));
|
|
19
|
+
return Boolean(issue.pull_request);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The PR's head branch name when it lives in this repo — that is the name
|
|
27
|
+
* the local checkout should carry, so committing on it and calling
|
|
28
|
+
* github_pr pushes back to the PR itself instead of opening a new one.
|
|
29
|
+
* Fork heads (unpushable with the repo-scoped token) and failed lookups
|
|
30
|
+
* return undefined; callers then fall back to the synthetic pr-<n>.
|
|
31
|
+
*/
|
|
32
|
+
export async function fetchPrHeadBranch(client, ref) {
|
|
33
|
+
try {
|
|
34
|
+
const pr = await githubRetry(() => client.getPullRequest(ref.owner, ref.repo, ref.number));
|
|
35
|
+
const sameRepo = pr.head?.repo?.full_name?.toLowerCase() === `${ref.owner}/${ref.repo}`.toLowerCase();
|
|
36
|
+
return sameRepo ? pr.head?.ref : undefined;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Host-side backend of the github_* tool pack (`PlatformGithubOps`),
|
|
44
|
+
* standalone from the messaging bot: it needs the API client, the
|
|
45
|
+
* conversation working dir, and optional Cloud Build access — not the poll
|
|
46
|
+
* loop, queues, or trigger gate. Tokens are minted per call, scoped to the
|
|
47
|
+
* conversation's repo, and never enter the sandbox.
|
|
48
|
+
*/
|
|
49
|
+
export class GithubOps {
|
|
50
|
+
constructor(client, config) {
|
|
51
|
+
this.client = client;
|
|
52
|
+
this.config = config;
|
|
53
|
+
/** Cloud Build ids seen in getChecks summaries → their GCP project. */
|
|
54
|
+
this.buildRef = new Map();
|
|
55
|
+
}
|
|
56
|
+
repoDir(conversationId) {
|
|
57
|
+
return conversationRepoDir(this.config.workingDir, conversationId);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Backs the `github_pr` tool: push a `pi/*` branch the agent prepared in
|
|
61
|
+
* the conversation's clone, then open a pull request as the App. The write
|
|
62
|
+
* token is minted per call, scoped to this one repo, and never enters the
|
|
63
|
+
* sandbox.
|
|
64
|
+
*/
|
|
65
|
+
async pushAndCreatePr(conversationId, request) {
|
|
66
|
+
const ref = parseGithubConversationId(conversationId);
|
|
67
|
+
const dir = this.repoDir(conversationId);
|
|
68
|
+
if (!existsSync(dir)) {
|
|
69
|
+
throw new Error("This conversation has no ./repo clone to push from.");
|
|
70
|
+
}
|
|
71
|
+
if (!GITHUB_PUSH_BRANCH_PATTERN.test(request.branch)) {
|
|
72
|
+
throw new Error(`Branch '${request.branch}' is not pushable: name it pi/<something> (e.g. pi/fix-${ref.number}).`);
|
|
73
|
+
}
|
|
74
|
+
const repository = await githubRetry(() => this.client.getRepository(ref.owner, ref.repo));
|
|
75
|
+
const base = request.base ?? repository.default_branch;
|
|
76
|
+
const token = await this.client.createScopedInstallationToken(ref.repo, {
|
|
77
|
+
contents: "write",
|
|
78
|
+
pull_requests: "write",
|
|
79
|
+
});
|
|
80
|
+
await pushBranch({ dir, branch: request.branch, token });
|
|
81
|
+
try {
|
|
82
|
+
const pr = await githubRetry(() => this.client.createPullRequest(ref.owner, ref.repo, {
|
|
83
|
+
title: request.title,
|
|
84
|
+
head: request.branch,
|
|
85
|
+
base,
|
|
86
|
+
body: request.body,
|
|
87
|
+
draft: request.draft,
|
|
88
|
+
}));
|
|
89
|
+
log.logInfo(`[${conversationId}] Opened PR #${pr.number}: ${pr.html_url}`);
|
|
90
|
+
return { number: pr.number, url: pr.html_url };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
// Idempotency: pushing more commits to a branch that already has an
|
|
94
|
+
// open PR updates that PR; the create call then 422s. Surface the
|
|
95
|
+
// existing PR instead of failing the fix-CI-push-again loop.
|
|
96
|
+
if (err instanceof GithubApiError &&
|
|
97
|
+
err.status === 422 &&
|
|
98
|
+
/already exists/i.test(err.message)) {
|
|
99
|
+
const existing = await this.client.findOpenPullRequestByBranch(ref.owner, ref.repo, request.branch);
|
|
100
|
+
if (existing) {
|
|
101
|
+
log.logInfo(`[${conversationId}] Pushed to existing PR #${existing.number}: ${existing.html_url}`);
|
|
102
|
+
return { number: existing.number, url: existing.html_url, updatedExisting: true };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Backs the `github_checks` tool: CI check runs for a branch the agent
|
|
110
|
+
* pushed, or for the conversation's PR head when no branch is given.
|
|
111
|
+
*/
|
|
112
|
+
async getChecks(conversationId, branch) {
|
|
113
|
+
const ref = parseGithubConversationId(conversationId);
|
|
114
|
+
let target = branch;
|
|
115
|
+
if (!target) {
|
|
116
|
+
let pr;
|
|
117
|
+
try {
|
|
118
|
+
pr = await githubRetry(() => this.client.getPullRequest(ref.owner, ref.repo, ref.number));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
pr = null;
|
|
122
|
+
}
|
|
123
|
+
if (!pr?.head) {
|
|
124
|
+
throw new Error("This conversation is not a pull request — pass the branch whose checks you want.");
|
|
125
|
+
}
|
|
126
|
+
target = pr.head.sha;
|
|
127
|
+
}
|
|
128
|
+
const runs = await githubRetry(() => this.client.listCheckRuns(ref.owner, ref.repo, target));
|
|
129
|
+
return runs.map((run) => {
|
|
130
|
+
const externalId = run.external_id?.trim() || null;
|
|
131
|
+
let buildLogAvailable = false;
|
|
132
|
+
// Cloud Build runs carry their build UUID in external_id and the GCP
|
|
133
|
+
// project in details_url. Remember project-by-build so getBuildLog only
|
|
134
|
+
// serves builds the agent actually saw on this repo's checks.
|
|
135
|
+
if (this.config.cloudBuild &&
|
|
136
|
+
run.app?.slug === CLOUD_BUILD_APP_SLUG &&
|
|
137
|
+
externalId &&
|
|
138
|
+
isCloudBuildId(externalId)) {
|
|
139
|
+
const project = projectFromDetailsUrl(run.details_url) ?? this.config.cloudBuild.projectFallback;
|
|
140
|
+
if (project) {
|
|
141
|
+
this.rememberBuild(externalId, project);
|
|
142
|
+
buildLogAvailable = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
id: run.id,
|
|
147
|
+
name: run.name,
|
|
148
|
+
status: run.status,
|
|
149
|
+
conclusion: run.conclusion,
|
|
150
|
+
url: run.html_url,
|
|
151
|
+
appSlug: run.app?.slug ?? null,
|
|
152
|
+
outputSummary: run.output?.summary?.trim() ? run.output.summary.slice(0, 500) : null,
|
|
153
|
+
externalId,
|
|
154
|
+
buildLogAvailable,
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
rememberBuild(buildId, project) {
|
|
159
|
+
// Insertion-ordered Map as a small LRU: re-insert on touch, prune oldest.
|
|
160
|
+
this.buildRef.delete(buildId);
|
|
161
|
+
this.buildRef.set(buildId, { project });
|
|
162
|
+
if (this.buildRef.size > 200) {
|
|
163
|
+
const oldest = this.buildRef.keys().next().value;
|
|
164
|
+
if (oldest !== undefined)
|
|
165
|
+
this.buildRef.delete(oldest);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Backs the Cloud Build log mode of `github_checks`. GCP credentials stay
|
|
170
|
+
* host-side (ADC via GOOGLE_APPLICATION_CREDENTIALS — e.g. a Workload
|
|
171
|
+
* Identity Federation external_account file); the build must have appeared
|
|
172
|
+
* in a github_checks summary first, which is also where its project is
|
|
173
|
+
* learned. Unreadable logs degrade to the console url, never a dead end.
|
|
174
|
+
*/
|
|
175
|
+
async getBuildLog(conversationId, buildId) {
|
|
176
|
+
if (!this.config.cloudBuild) {
|
|
177
|
+
throw new Error("Cloud Build log access is not configured on this host (no GCP credentials).");
|
|
178
|
+
}
|
|
179
|
+
const known = this.buildRef.get(buildId);
|
|
180
|
+
if (!known) {
|
|
181
|
+
throw new Error(`Unknown build id ${buildId}: run github_checks first and take a [build …] id from ` +
|
|
182
|
+
`the summary.`);
|
|
183
|
+
}
|
|
184
|
+
let logText;
|
|
185
|
+
try {
|
|
186
|
+
logText = await fetchCloudBuildLog({
|
|
187
|
+
tokenProvider: this.config.cloudBuild.tokenProvider,
|
|
188
|
+
project: known.project,
|
|
189
|
+
buildId,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (err instanceof CloudBuildLogUnavailableError) {
|
|
194
|
+
throw new Error(`${err.message}${err.consoleUrl ? ` Console: ${err.consoleUrl}` : ""} Use the check's ` +
|
|
195
|
+
`summary/url from github_checks, or reproduce the failure locally in ./repo.`, { cause: err });
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
return truncateLogTail(logText);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Backs the `github_sync` tool: refresh the conversation's ./repo clone
|
|
203
|
+
* from origin with an ephemeral read token. Fetch-only when moving the
|
|
204
|
+
* checkout could lose agent work; the formatted report tells the agent
|
|
205
|
+
* what happened and what to do next.
|
|
206
|
+
*/
|
|
207
|
+
async syncRepo(conversationId, branch) {
|
|
208
|
+
const ref = parseGithubConversationId(conversationId);
|
|
209
|
+
const dir = this.repoDir(conversationId);
|
|
210
|
+
if (!existsSync(dir)) {
|
|
211
|
+
throw new Error("This conversation has no ./repo clone to sync.");
|
|
212
|
+
}
|
|
213
|
+
const token = await this.client.createScopedInstallationToken(ref.repo, {
|
|
214
|
+
contents: "read",
|
|
215
|
+
});
|
|
216
|
+
let prNumber;
|
|
217
|
+
let prHeadBranch;
|
|
218
|
+
let defaultBranch;
|
|
219
|
+
if (!branch) {
|
|
220
|
+
if (await fetchIsPr(this.client, ref)) {
|
|
221
|
+
prNumber = ref.number;
|
|
222
|
+
prHeadBranch = await fetchPrHeadBranch(this.client, ref);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const repository = await githubRetry(() => this.client.getRepository(ref.owner, ref.repo));
|
|
226
|
+
defaultBranch = repository.default_branch;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const result = await syncRepo({ dir, token, branch, prNumber, prHeadBranch, defaultBranch });
|
|
230
|
+
if (result.updatedCheckout) {
|
|
231
|
+
return `Updated ./repo: branch ${result.target} is now at ${result.fetchedSha.slice(0, 12)}.`;
|
|
232
|
+
}
|
|
233
|
+
const reasons = [];
|
|
234
|
+
if (result.currentBranch !== result.target) {
|
|
235
|
+
reasons.push(`the checkout is on '${result.currentBranch}', not '${result.target}'`);
|
|
236
|
+
}
|
|
237
|
+
if (result.dirty) {
|
|
238
|
+
reasons.push("the working tree has uncommitted changes");
|
|
239
|
+
}
|
|
240
|
+
if (result.localCommits > 0) {
|
|
241
|
+
reasons.push(`'${result.target}' has ${result.localCommits} local commit(s) not on origin`);
|
|
242
|
+
}
|
|
243
|
+
return (`Fetched ${result.target} to FETCH_HEAD (${result.fetchedSha.slice(0, 12)}) but left the ` +
|
|
244
|
+
`checkout alone: ${reasons.join("; ")}. Merge or rebase FETCH_HEAD yourself ` +
|
|
245
|
+
`(e.g. git merge FETCH_HEAD), or commit your work to a pi/* branch first.`);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Backs the `github_read` tool: PR/issue metadata the ./repo clone cannot
|
|
249
|
+
* provide (diff stats, review state, other issues in the repo). Owner/repo
|
|
250
|
+
* always come from the conversation id, so reads never leave this repo.
|
|
251
|
+
*/
|
|
252
|
+
async readGithub(conversationId, request) {
|
|
253
|
+
const ref = parseGithubConversationId(conversationId);
|
|
254
|
+
const number = request.number ?? ref.number;
|
|
255
|
+
switch (request.action) {
|
|
256
|
+
case "pr": {
|
|
257
|
+
const pr = await githubRetry(() => this.client.getPullRequest(ref.owner, ref.repo, number));
|
|
258
|
+
return { kind: "pr", pr };
|
|
259
|
+
}
|
|
260
|
+
case "pr_files": {
|
|
261
|
+
const files = await githubRetry(() => this.client.listPullRequestFiles(ref.owner, ref.repo, number));
|
|
262
|
+
return { kind: "pr_files", files };
|
|
263
|
+
}
|
|
264
|
+
case "pr_reviews": {
|
|
265
|
+
const [reviews, threads] = await Promise.all([
|
|
266
|
+
githubRetry(() => this.client.listPullRequestReviews(ref.owner, ref.repo, number)),
|
|
267
|
+
githubRetry(() => this.client.listPullReviewComments(ref.owner, ref.repo, number)),
|
|
268
|
+
]);
|
|
269
|
+
return { kind: "pr_reviews", reviews, threads };
|
|
270
|
+
}
|
|
271
|
+
case "issue": {
|
|
272
|
+
const issue = await githubRetry(() => this.client.getIssue(ref.owner, ref.repo, number));
|
|
273
|
+
return { kind: "issue", issue };
|
|
274
|
+
}
|
|
275
|
+
case "comments": {
|
|
276
|
+
const comments = await githubRetry(() => this.client.listIssueComments(ref.owner, ref.repo, number));
|
|
277
|
+
return { kind: "comments", comments };
|
|
278
|
+
}
|
|
279
|
+
case "list": {
|
|
280
|
+
const issues = await githubRetry(() => this.client.listIssues(ref.owner, ref.repo, {
|
|
281
|
+
state: request.state,
|
|
282
|
+
labels: request.labels,
|
|
283
|
+
creator: request.creator,
|
|
284
|
+
}));
|
|
285
|
+
return { kind: "list", issues };
|
|
286
|
+
}
|
|
287
|
+
default:
|
|
288
|
+
throw new Error(`Unknown github_read action: ${String(request.action)}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Backs the `github_issue` tool: labels, assignees, and open/closed state
|
|
293
|
+
* on any issue of this conversation's repo (triage). Same-repo by
|
|
294
|
+
* construction — owner/repo come from the conversation id; the action enum
|
|
295
|
+
* is closed, so lock/delete/transfer cannot be expressed at all.
|
|
296
|
+
*/
|
|
297
|
+
async manageIssue(conversationId, request) {
|
|
298
|
+
const ref = parseGithubConversationId(conversationId);
|
|
299
|
+
const number = request.number ?? ref.number;
|
|
300
|
+
try {
|
|
301
|
+
switch (request.action) {
|
|
302
|
+
case "add_labels": {
|
|
303
|
+
if (!request.labels?.length) {
|
|
304
|
+
throw new Error("add_labels requires a non-empty labels array.");
|
|
305
|
+
}
|
|
306
|
+
await githubRetry(() => this.client.addIssueLabels(ref.owner, ref.repo, number, request.labels));
|
|
307
|
+
return `Added label(s) ${request.labels.join(", ")} to #${number}.`;
|
|
308
|
+
}
|
|
309
|
+
case "remove_label": {
|
|
310
|
+
if (!request.label) {
|
|
311
|
+
throw new Error("remove_label requires a label name.");
|
|
312
|
+
}
|
|
313
|
+
await githubRetry(() => this.client.removeIssueLabel(ref.owner, ref.repo, number, request.label));
|
|
314
|
+
return `Removed label ${request.label} from #${number}.`;
|
|
315
|
+
}
|
|
316
|
+
case "add_assignees": {
|
|
317
|
+
if (!request.assignees?.length) {
|
|
318
|
+
throw new Error("add_assignees requires a non-empty assignees array.");
|
|
319
|
+
}
|
|
320
|
+
await githubRetry(() => this.client.addIssueAssignees(ref.owner, ref.repo, number, request.assignees));
|
|
321
|
+
return `Assigned ${request.assignees.map((login) => `@${login}`).join(", ")} to #${number}.`;
|
|
322
|
+
}
|
|
323
|
+
case "remove_assignees": {
|
|
324
|
+
if (!request.assignees?.length) {
|
|
325
|
+
throw new Error("remove_assignees requires a non-empty assignees array.");
|
|
326
|
+
}
|
|
327
|
+
await githubRetry(() => this.client.removeIssueAssignees(ref.owner, ref.repo, number, request.assignees));
|
|
328
|
+
return `Unassigned ${request.assignees.map((login) => `@${login}`).join(", ")} from #${number}.`;
|
|
329
|
+
}
|
|
330
|
+
case "close": {
|
|
331
|
+
await githubRetry(() => this.client.updateIssueState(ref.owner, ref.repo, number, "closed", request.state_reason));
|
|
332
|
+
return `Closed #${number}${request.state_reason ? ` as ${request.state_reason}` : ""}.`;
|
|
333
|
+
}
|
|
334
|
+
case "reopen": {
|
|
335
|
+
await githubRetry(() => this.client.updateIssueState(ref.owner, ref.repo, number, "open"));
|
|
336
|
+
return `Reopened #${number}.`;
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
throw new Error(`Unknown github_issue action: ${String(request.action)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
if (err instanceof GithubApiError && err.status === 404) {
|
|
344
|
+
throw new Error(`Issue #${number} not found in ${ref.owner}/${ref.repo}.`, { cause: err });
|
|
345
|
+
}
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Backs the `github_review_reply` tool: answer inside one inline review
|
|
351
|
+
* thread of this conversation's PR. The comment id comes from an
|
|
352
|
+
* `[PR review comment rc-<id> …]` message; GitHub validates it belongs to
|
|
353
|
+
* this PR (404 otherwise, translated into agent-actionable guidance).
|
|
354
|
+
*/
|
|
355
|
+
async replyToReviewThread(conversationId, commentId, body) {
|
|
356
|
+
if (!Number.isInteger(commentId) || commentId <= 0) {
|
|
357
|
+
throw new Error("comment_id must be the numeric id from an [PR review comment rc-<id> …] message.");
|
|
358
|
+
}
|
|
359
|
+
const ref = parseGithubConversationId(conversationId);
|
|
360
|
+
const text = body.length > GITHUB_MAX_COMMENT_LENGTH
|
|
361
|
+
? `${body.slice(0, GITHUB_MAX_COMMENT_LENGTH)}\n…(truncated)`
|
|
362
|
+
: body;
|
|
363
|
+
try {
|
|
364
|
+
const reply = await githubRetry(() => this.client.replyToReviewComment(ref.owner, ref.repo, ref.number, commentId, text));
|
|
365
|
+
return {
|
|
366
|
+
url: `https://github.com/${ref.owner}/${ref.repo}/pull/${ref.number}#discussion_r${reply.id}`,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
if (err instanceof GithubApiError && err.status === 404) {
|
|
371
|
+
throw new Error(`Comment ${commentId} is not a review comment on this PR. Take the id from an ` +
|
|
372
|
+
`[PR review comment rc-<id> …] message in this conversation; do not guess ids.`, { cause: err });
|
|
373
|
+
}
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Backs the log mode of `github_checks`: the tail of one CI job's log so
|
|
379
|
+
* the agent can diagnose a failing check without sandbox credentials. Only
|
|
380
|
+
* GitHub Actions checks have logs on GitHub — external CI apps (e.g. Cloud
|
|
381
|
+
* Build) keep theirs on their own service, so a 404 here is translated
|
|
382
|
+
* into guidance instead of a dead end.
|
|
383
|
+
*/
|
|
384
|
+
async getJobLog(conversationId, jobId) {
|
|
385
|
+
if (!Number.isInteger(jobId) || jobId <= 0) {
|
|
386
|
+
throw new Error("job_id must be a positive Actions job id taken from a [job …] entry in the github_checks summary.");
|
|
387
|
+
}
|
|
388
|
+
const ref = parseGithubConversationId(conversationId);
|
|
389
|
+
let logText;
|
|
390
|
+
try {
|
|
391
|
+
logText = await githubRetry(() => this.client.getJobLog(ref.owner, ref.repo, jobId));
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
if (err instanceof GithubApiError && err.status === 404) {
|
|
395
|
+
throw new Error(`No GitHub Actions log for job ${jobId}. Logs are only available for checks reported ` +
|
|
396
|
+
`by github-actions; external CI (e.g. Cloud Build) keeps logs on its own service — ` +
|
|
397
|
+
`use that check's summary/url from github_checks, or reproduce the failure locally ` +
|
|
398
|
+
`in ./repo instead. Do not retry with other ids.`, { cause: err });
|
|
399
|
+
}
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
return truncateLogTail(logText);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=github-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-ops.js","sourceRoot":"","sources":["../../../src/adapters/github/github-ops.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"fs\";\nimport * as log from \"../../log.js\";\nimport { GithubApiError, GithubClient, GITHUB_MAX_COMMENT_LENGTH, githubRetry } from \"./client.js\";\nimport {\n CLOUD_BUILD_APP_SLUG,\n CloudBuildLogUnavailableError,\n fetchCloudBuildLog,\n isCloudBuildId,\n projectFromDetailsUrl,\n} from \"./cloudbuild.js\";\nimport { parseGithubConversationId } from \"./ids.js\";\nimport { conversationRepoDir, GITHUB_PUSH_BRANCH_PATTERN, pushBranch, syncRepo } from \"./repo.js\";\nimport type {\n GithubBotConfig,\n GithubCheckSummary,\n GithubConversationRef,\n GithubIssueRequest,\n GithubPrRequest,\n GithubPrResult,\n GithubReadRequest,\n GithubReadResult,\n PlatformGithubOps,\n} from \"./types.js\";\n\n/** Both CI log modes tail-truncate to this many characters. */\nconst MAX_LOG_CHARS = 20000;\n\nfunction truncateLogTail(logText: string): string {\n return logText.length > MAX_LOG_CHARS\n ? `…(truncated to the last ${MAX_LOG_CHARS} chars)\\n${logText.slice(-MAX_LOG_CHARS)}`\n : logText;\n}\n\n/** Whether the conversation's issue is a PR, for callers where the triggering\n * item didn't carry that knowledge. Errs toward plain issue. */\nexport async function fetchIsPr(\n client: GithubClient,\n ref: GithubConversationRef,\n): Promise<boolean> {\n try {\n const issue = await githubRetry(() => client.getIssue(ref.owner, ref.repo, ref.number));\n return Boolean(issue.pull_request);\n } catch {\n return false;\n }\n}\n\n/**\n * The PR's head branch name when it lives in this repo — that is the name\n * the local checkout should carry, so committing on it and calling\n * github_pr pushes back to the PR itself instead of opening a new one.\n * Fork heads (unpushable with the repo-scoped token) and failed lookups\n * return undefined; callers then fall back to the synthetic pr-<n>.\n */\nexport async function fetchPrHeadBranch(\n client: GithubClient,\n ref: GithubConversationRef,\n): Promise<string | undefined> {\n try {\n const pr = await githubRetry(() => client.getPullRequest(ref.owner, ref.repo, ref.number));\n const sameRepo =\n pr.head?.repo?.full_name?.toLowerCase() === `${ref.owner}/${ref.repo}`.toLowerCase();\n return sameRepo ? pr.head?.ref : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Host-side backend of the github_* tool pack (`PlatformGithubOps`),\n * standalone from the messaging bot: it needs the API client, the\n * conversation working dir, and optional Cloud Build access — not the poll\n * loop, queues, or trigger gate. Tokens are minted per call, scoped to the\n * conversation's repo, and never enter the sandbox.\n */\nexport class GithubOps implements PlatformGithubOps {\n /** Cloud Build ids seen in getChecks summaries → their GCP project. */\n private buildRef = new Map<string, { project: string }>();\n\n constructor(\n private readonly client: GithubClient,\n private readonly config: {\n workingDir: string;\n cloudBuild?: GithubBotConfig[\"cloudBuild\"];\n },\n ) {}\n\n private repoDir(conversationId: string): string {\n return conversationRepoDir(this.config.workingDir, conversationId);\n }\n\n /**\n * Backs the `github_pr` tool: push a `pi/*` branch the agent prepared in\n * the conversation's clone, then open a pull request as the App. The write\n * token is minted per call, scoped to this one repo, and never enters the\n * sandbox.\n */\n async pushAndCreatePr(conversationId: string, request: GithubPrRequest): Promise<GithubPrResult> {\n const ref = parseGithubConversationId(conversationId);\n const dir = this.repoDir(conversationId);\n if (!existsSync(dir)) {\n throw new Error(\"This conversation has no ./repo clone to push from.\");\n }\n if (!GITHUB_PUSH_BRANCH_PATTERN.test(request.branch)) {\n throw new Error(\n `Branch '${request.branch}' is not pushable: name it pi/<something> (e.g. pi/fix-${ref.number}).`,\n );\n }\n const repository = await githubRetry(() => this.client.getRepository(ref.owner, ref.repo));\n const base = request.base ?? repository.default_branch;\n const token = await this.client.createScopedInstallationToken(ref.repo, {\n contents: \"write\",\n pull_requests: \"write\",\n });\n await pushBranch({ dir, branch: request.branch, token });\n try {\n const pr = await githubRetry(() =>\n this.client.createPullRequest(ref.owner, ref.repo, {\n title: request.title,\n head: request.branch,\n base,\n body: request.body,\n draft: request.draft,\n }),\n );\n log.logInfo(`[${conversationId}] Opened PR #${pr.number}: ${pr.html_url}`);\n return { number: pr.number, url: pr.html_url };\n } catch (err) {\n // Idempotency: pushing more commits to a branch that already has an\n // open PR updates that PR; the create call then 422s. Surface the\n // existing PR instead of failing the fix-CI-push-again loop.\n if (\n err instanceof GithubApiError &&\n err.status === 422 &&\n /already exists/i.test(err.message)\n ) {\n const existing = await this.client.findOpenPullRequestByBranch(\n ref.owner,\n ref.repo,\n request.branch,\n );\n if (existing) {\n log.logInfo(\n `[${conversationId}] Pushed to existing PR #${existing.number}: ${existing.html_url}`,\n );\n return { number: existing.number, url: existing.html_url, updatedExisting: true };\n }\n }\n throw err;\n }\n }\n\n /**\n * Backs the `github_checks` tool: CI check runs for a branch the agent\n * pushed, or for the conversation's PR head when no branch is given.\n */\n async getChecks(conversationId: string, branch?: string): Promise<GithubCheckSummary[]> {\n const ref = parseGithubConversationId(conversationId);\n let target = branch;\n if (!target) {\n let pr;\n try {\n pr = await githubRetry(() => this.client.getPullRequest(ref.owner, ref.repo, ref.number));\n } catch {\n pr = null;\n }\n if (!pr?.head) {\n throw new Error(\n \"This conversation is not a pull request — pass the branch whose checks you want.\",\n );\n }\n target = pr.head.sha;\n }\n const runs = await githubRetry(() => this.client.listCheckRuns(ref.owner, ref.repo, target));\n return runs.map((run) => {\n const externalId = run.external_id?.trim() || null;\n let buildLogAvailable = false;\n // Cloud Build runs carry their build UUID in external_id and the GCP\n // project in details_url. Remember project-by-build so getBuildLog only\n // serves builds the agent actually saw on this repo's checks.\n if (\n this.config.cloudBuild &&\n run.app?.slug === CLOUD_BUILD_APP_SLUG &&\n externalId &&\n isCloudBuildId(externalId)\n ) {\n const project =\n projectFromDetailsUrl(run.details_url) ?? this.config.cloudBuild.projectFallback;\n if (project) {\n this.rememberBuild(externalId, project);\n buildLogAvailable = true;\n }\n }\n return {\n id: run.id,\n name: run.name,\n status: run.status,\n conclusion: run.conclusion,\n url: run.html_url,\n appSlug: run.app?.slug ?? null,\n outputSummary: run.output?.summary?.trim() ? run.output.summary.slice(0, 500) : null,\n externalId,\n buildLogAvailable,\n };\n });\n }\n\n private rememberBuild(buildId: string, project: string): void {\n // Insertion-ordered Map as a small LRU: re-insert on touch, prune oldest.\n this.buildRef.delete(buildId);\n this.buildRef.set(buildId, { project });\n if (this.buildRef.size > 200) {\n const oldest = this.buildRef.keys().next().value;\n if (oldest !== undefined) this.buildRef.delete(oldest);\n }\n }\n\n /**\n * Backs the Cloud Build log mode of `github_checks`. GCP credentials stay\n * host-side (ADC via GOOGLE_APPLICATION_CREDENTIALS — e.g. a Workload\n * Identity Federation external_account file); the build must have appeared\n * in a github_checks summary first, which is also where its project is\n * learned. Unreadable logs degrade to the console url, never a dead end.\n */\n async getBuildLog(conversationId: string, buildId: string): Promise<string> {\n if (!this.config.cloudBuild) {\n throw new Error(\n \"Cloud Build log access is not configured on this host (no GCP credentials).\",\n );\n }\n const known = this.buildRef.get(buildId);\n if (!known) {\n throw new Error(\n `Unknown build id ${buildId}: run github_checks first and take a [build …] id from ` +\n `the summary.`,\n );\n }\n let logText: string;\n try {\n logText = await fetchCloudBuildLog({\n tokenProvider: this.config.cloudBuild.tokenProvider,\n project: known.project,\n buildId,\n });\n } catch (err) {\n if (err instanceof CloudBuildLogUnavailableError) {\n throw new Error(\n `${err.message}${err.consoleUrl ? ` Console: ${err.consoleUrl}` : \"\"} Use the check's ` +\n `summary/url from github_checks, or reproduce the failure locally in ./repo.`,\n { cause: err },\n );\n }\n throw err;\n }\n return truncateLogTail(logText);\n }\n\n /**\n * Backs the `github_sync` tool: refresh the conversation's ./repo clone\n * from origin with an ephemeral read token. Fetch-only when moving the\n * checkout could lose agent work; the formatted report tells the agent\n * what happened and what to do next.\n */\n async syncRepo(conversationId: string, branch?: string): Promise<string> {\n const ref = parseGithubConversationId(conversationId);\n const dir = this.repoDir(conversationId);\n if (!existsSync(dir)) {\n throw new Error(\"This conversation has no ./repo clone to sync.\");\n }\n const token = await this.client.createScopedInstallationToken(ref.repo, {\n contents: \"read\",\n });\n let prNumber: number | undefined;\n let prHeadBranch: string | undefined;\n let defaultBranch: string | undefined;\n if (!branch) {\n if (await fetchIsPr(this.client, ref)) {\n prNumber = ref.number;\n prHeadBranch = await fetchPrHeadBranch(this.client, ref);\n } else {\n const repository = await githubRetry(() => this.client.getRepository(ref.owner, ref.repo));\n defaultBranch = repository.default_branch;\n }\n }\n const result = await syncRepo({ dir, token, branch, prNumber, prHeadBranch, defaultBranch });\n\n if (result.updatedCheckout) {\n return `Updated ./repo: branch ${result.target} is now at ${result.fetchedSha.slice(0, 12)}.`;\n }\n const reasons: string[] = [];\n if (result.currentBranch !== result.target) {\n reasons.push(`the checkout is on '${result.currentBranch}', not '${result.target}'`);\n }\n if (result.dirty) {\n reasons.push(\"the working tree has uncommitted changes\");\n }\n if (result.localCommits > 0) {\n reasons.push(`'${result.target}' has ${result.localCommits} local commit(s) not on origin`);\n }\n return (\n `Fetched ${result.target} to FETCH_HEAD (${result.fetchedSha.slice(0, 12)}) but left the ` +\n `checkout alone: ${reasons.join(\"; \")}. Merge or rebase FETCH_HEAD yourself ` +\n `(e.g. git merge FETCH_HEAD), or commit your work to a pi/* branch first.`\n );\n }\n\n /**\n * Backs the `github_read` tool: PR/issue metadata the ./repo clone cannot\n * provide (diff stats, review state, other issues in the repo). Owner/repo\n * always come from the conversation id, so reads never leave this repo.\n */\n async readGithub(conversationId: string, request: GithubReadRequest): Promise<GithubReadResult> {\n const ref = parseGithubConversationId(conversationId);\n const number = request.number ?? ref.number;\n switch (request.action) {\n case \"pr\": {\n const pr = await githubRetry(() => this.client.getPullRequest(ref.owner, ref.repo, number));\n return { kind: \"pr\", pr };\n }\n case \"pr_files\": {\n const files = await githubRetry(() =>\n this.client.listPullRequestFiles(ref.owner, ref.repo, number),\n );\n return { kind: \"pr_files\", files };\n }\n case \"pr_reviews\": {\n const [reviews, threads] = await Promise.all([\n githubRetry(() => this.client.listPullRequestReviews(ref.owner, ref.repo, number)),\n githubRetry(() => this.client.listPullReviewComments(ref.owner, ref.repo, number)),\n ]);\n return { kind: \"pr_reviews\", reviews, threads };\n }\n case \"issue\": {\n const issue = await githubRetry(() => this.client.getIssue(ref.owner, ref.repo, number));\n return { kind: \"issue\", issue };\n }\n case \"comments\": {\n const comments = await githubRetry(() =>\n this.client.listIssueComments(ref.owner, ref.repo, number),\n );\n return { kind: \"comments\", comments };\n }\n case \"list\": {\n const issues = await githubRetry(() =>\n this.client.listIssues(ref.owner, ref.repo, {\n state: request.state,\n labels: request.labels,\n creator: request.creator,\n }),\n );\n return { kind: \"list\", issues };\n }\n default:\n throw new Error(`Unknown github_read action: ${String(request.action)}`);\n }\n }\n\n /**\n * Backs the `github_issue` tool: labels, assignees, and open/closed state\n * on any issue of this conversation's repo (triage). Same-repo by\n * construction — owner/repo come from the conversation id; the action enum\n * is closed, so lock/delete/transfer cannot be expressed at all.\n */\n async manageIssue(conversationId: string, request: GithubIssueRequest): Promise<string> {\n const ref = parseGithubConversationId(conversationId);\n const number = request.number ?? ref.number;\n try {\n switch (request.action) {\n case \"add_labels\": {\n if (!request.labels?.length) {\n throw new Error(\"add_labels requires a non-empty labels array.\");\n }\n await githubRetry(() =>\n this.client.addIssueLabels(ref.owner, ref.repo, number, request.labels!),\n );\n return `Added label(s) ${request.labels.join(\", \")} to #${number}.`;\n }\n case \"remove_label\": {\n if (!request.label) {\n throw new Error(\"remove_label requires a label name.\");\n }\n await githubRetry(() =>\n this.client.removeIssueLabel(ref.owner, ref.repo, number, request.label!),\n );\n return `Removed label ${request.label} from #${number}.`;\n }\n case \"add_assignees\": {\n if (!request.assignees?.length) {\n throw new Error(\"add_assignees requires a non-empty assignees array.\");\n }\n await githubRetry(() =>\n this.client.addIssueAssignees(ref.owner, ref.repo, number, request.assignees!),\n );\n return `Assigned ${request.assignees.map((login) => `@${login}`).join(\", \")} to #${number}.`;\n }\n case \"remove_assignees\": {\n if (!request.assignees?.length) {\n throw new Error(\"remove_assignees requires a non-empty assignees array.\");\n }\n await githubRetry(() =>\n this.client.removeIssueAssignees(ref.owner, ref.repo, number, request.assignees!),\n );\n return `Unassigned ${request.assignees.map((login) => `@${login}`).join(\", \")} from #${number}.`;\n }\n case \"close\": {\n await githubRetry(() =>\n this.client.updateIssueState(\n ref.owner,\n ref.repo,\n number,\n \"closed\",\n request.state_reason,\n ),\n );\n return `Closed #${number}${request.state_reason ? ` as ${request.state_reason}` : \"\"}.`;\n }\n case \"reopen\": {\n await githubRetry(() =>\n this.client.updateIssueState(ref.owner, ref.repo, number, \"open\"),\n );\n return `Reopened #${number}.`;\n }\n default:\n throw new Error(`Unknown github_issue action: ${String(request.action)}`);\n }\n } catch (err) {\n if (err instanceof GithubApiError && err.status === 404) {\n throw new Error(`Issue #${number} not found in ${ref.owner}/${ref.repo}.`, { cause: err });\n }\n throw err;\n }\n }\n\n /**\n * Backs the `github_review_reply` tool: answer inside one inline review\n * thread of this conversation's PR. The comment id comes from an\n * `[PR review comment rc-<id> …]` message; GitHub validates it belongs to\n * this PR (404 otherwise, translated into agent-actionable guidance).\n */\n async replyToReviewThread(\n conversationId: string,\n commentId: number,\n body: string,\n ): Promise<{ url: string }> {\n if (!Number.isInteger(commentId) || commentId <= 0) {\n throw new Error(\n \"comment_id must be the numeric id from an [PR review comment rc-<id> …] message.\",\n );\n }\n const ref = parseGithubConversationId(conversationId);\n const text =\n body.length > GITHUB_MAX_COMMENT_LENGTH\n ? `${body.slice(0, GITHUB_MAX_COMMENT_LENGTH)}\\n…(truncated)`\n : body;\n try {\n const reply = await githubRetry(() =>\n this.client.replyToReviewComment(ref.owner, ref.repo, ref.number, commentId, text),\n );\n return {\n url: `https://github.com/${ref.owner}/${ref.repo}/pull/${ref.number}#discussion_r${reply.id}`,\n };\n } catch (err) {\n if (err instanceof GithubApiError && err.status === 404) {\n throw new Error(\n `Comment ${commentId} is not a review comment on this PR. Take the id from an ` +\n `[PR review comment rc-<id> …] message in this conversation; do not guess ids.`,\n { cause: err },\n );\n }\n throw err;\n }\n }\n\n /**\n * Backs the log mode of `github_checks`: the tail of one CI job's log so\n * the agent can diagnose a failing check without sandbox credentials. Only\n * GitHub Actions checks have logs on GitHub — external CI apps (e.g. Cloud\n * Build) keep theirs on their own service, so a 404 here is translated\n * into guidance instead of a dead end.\n */\n async getJobLog(conversationId: string, jobId: number): Promise<string> {\n if (!Number.isInteger(jobId) || jobId <= 0) {\n throw new Error(\n \"job_id must be a positive Actions job id taken from a [job …] entry in the github_checks summary.\",\n );\n }\n const ref = parseGithubConversationId(conversationId);\n let logText: string;\n try {\n logText = await githubRetry(() => this.client.getJobLog(ref.owner, ref.repo, jobId));\n } catch (err) {\n if (err instanceof GithubApiError && err.status === 404) {\n throw new Error(\n `No GitHub Actions log for job ${jobId}. Logs are only available for checks reported ` +\n `by github-actions; external CI (e.g. Cloud Build) keeps logs on its own service — ` +\n `use that check's summary/url from github_checks, or reproduce the failure locally ` +\n `in ./repo instead. Do not retry with other ids.`,\n { cause: err },\n );\n }\n throw err;\n }\n return truncateLogTail(logText);\n }\n}\n"]}
|
|
@@ -5,6 +5,8 @@ import type { CloneRepoOptions, PushBranchOptions, SyncRepoOptions, SyncRepoResu
|
|
|
5
5
|
* only ever produce reviewable branches, never touch mainline directly.
|
|
6
6
|
*/
|
|
7
7
|
export declare const GITHUB_PUSH_BRANCH_PATTERN: RegExp;
|
|
8
|
+
/** The conversation's clone location (bind-mounted into the sandbox as ./repo). */
|
|
9
|
+
export declare function conversationRepoDir(workingDir: string, conversationId: string): string;
|
|
8
10
|
/**
|
|
9
11
|
* Shallow-clone a repo into a conversation dir and preconfigure the bot's
|
|
10
12
|
* commit identity. For PR conversations, additionally fetch the PR head and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAwC,CAAC;AAEhF,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAEtF;AA0CD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8ChF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1E"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "child_process";
|
|
2
|
+
import { join } from "path";
|
|
2
3
|
import { promisify } from "util";
|
|
3
4
|
const execFileAsync = promisify(execFile);
|
|
4
5
|
const GIT_TIMEOUT_MS = 120_000;
|
|
@@ -8,6 +9,10 @@ const GIT_TIMEOUT_MS = 120_000;
|
|
|
8
9
|
* only ever produce reviewable branches, never touch mainline directly.
|
|
9
10
|
*/
|
|
10
11
|
export const GITHUB_PUSH_BRANCH_PATTERN = /^pi\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;
|
|
12
|
+
/** The conversation's clone location (bind-mounted into the sandbox as ./repo). */
|
|
13
|
+
export function conversationRepoDir(workingDir, conversationId) {
|
|
14
|
+
return join(workingDir, conversationId, "repo");
|
|
15
|
+
}
|
|
11
16
|
/**
|
|
12
17
|
* Pass the ephemeral token as a per-invocation header, never in the remote
|
|
13
18
|
* URL: nothing credential-shaped may end up in `.git/config`, which lives in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport type {\n CloneRepoOptions,\n PushBranchOptions,\n SyncRepoOptions,\n SyncRepoResult,\n} from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_TIMEOUT_MS = 120_000;\n\n/**\n * Branches the bot may push: `pi/<name>`. Everything else — including the\n * repo's default branch by construction — is refused, so a driven agent can\n * only ever produce reviewable branches, never touch mainline directly.\n */\nexport const GITHUB_PUSH_BRANCH_PATTERN = /^pi\\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;\n\n/**\n * Pass the ephemeral token as a per-invocation header, never in the remote\n * URL: nothing credential-shaped may end up in `.git/config`, which lives in\n * the conversation dir and is bind-mounted into the sandbox.\n */\nfunction gitAuthArgs(token: string): string[] {\n const basic = Buffer.from(`x-access-token:${token}`).toString(\"base64\");\n return [\"-c\", `http.extraheader=Authorization: basic ${basic}`];\n}\n\nasync function git(args: string[], token?: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [...(token ? gitAuthArgs(token) : []), ...args], {\n timeout: GIT_TIMEOUT_MS,\n env: { ...process.env, GIT_TERMINAL_PROMPT: \"0\" },\n });\n return stdout;\n}\n\n/**\n * Git itself rejects these ref shapes; the check just keeps an API-supplied\n * branch name that would parse as a git option (leading dash) or an invalid\n * ref out of argv entirely instead of surfacing as a git error mid-clone.\n */\nfunction isSafeBranchName(name: string): boolean {\n return (\n name.length > 0 && !name.startsWith(\"-\") && !name.includes(\"..\") && !/[\\s~^:?*[\\\\]/.test(name)\n );\n}\n\n/**\n * Local checkout name for a PR conversation: the PR's real head branch when\n * known (same-repo heads only — the caller resolves that), falling back to\n * the synthetic `pr-<n>` for fork PRs, unresolvable heads, and unsafe names.\n * Using the real name is what lets the agent update the PR in place: commit\n * on a `pi/*` head and `github_pr` pushes back to the same branch.\n */\nfunction prCheckoutName(prNumber: number, prHeadBranch?: string): string {\n return prHeadBranch && isSafeBranchName(prHeadBranch) ? prHeadBranch : `pr-${prNumber}`;\n}\n\n/**\n * Shallow-clone a repo into a conversation dir and preconfigure the bot's\n * commit identity. For PR conversations, additionally fetch the PR head and\n * check it out so the agent starts on the code under review — under its real\n * branch name when the caller resolved one, which is what lets the agent push\n * fixes back to the PR itself.\n */\nexport async function cloneRepo(options: CloneRepoOptions): Promise<void> {\n await git([\"clone\", \"--depth\", \"50\", options.url, options.dir], options.token);\n await git([\"-C\", options.dir, \"config\", \"user.name\", options.botLogin]);\n await git([\"-C\", options.dir, \"config\", \"user.email\", options.botEmail]);\n if (options.prNumber !== undefined) {\n await git(\n [\"-C\", options.dir, \"fetch\", \"--depth\", \"50\", \"origin\", `pull/${options.prNumber}/head`],\n options.token,\n );\n await git([\n \"-C\",\n options.dir,\n \"checkout\",\n \"-B\",\n prCheckoutName(options.prNumber, options.prHeadBranch),\n \"FETCH_HEAD\",\n ]);\n }\n}\n\n/**\n * Refresh a conversation clone from origin. Always fetches; only moves the\n * checkout when that provably cannot lose agent work: the target branch is\n * the one checked out, the tree is clean, and it has no commits of its own\n * (`checkout -B` then handles fast-forward and force-pushed heads alike).\n * Anything else stays fetch-only — FETCH_HEAD is left for the agent, which\n * has full credential-less git inside the sandbox, to merge or rebase itself.\n */\nexport async function syncRepo(options: SyncRepoOptions): Promise<SyncRepoResult> {\n const { dir, token } = options;\n let refspec: string;\n let target: string;\n if (options.branch) {\n refspec = options.branch;\n target = options.branch;\n } else if (options.prNumber !== undefined) {\n refspec = `pull/${options.prNumber}/head`;\n target = prCheckoutName(options.prNumber, options.prHeadBranch);\n } else if (options.defaultBranch) {\n refspec = options.defaultBranch;\n target = options.defaultBranch;\n } else {\n throw new Error(\"syncRepo needs a branch, a PR number, or the default branch\");\n }\n\n await git([\"-C\", dir, \"fetch\", \"--depth\", \"50\", \"origin\", refspec], token);\n const fetchedSha = (await git([\"-C\", dir, \"rev-parse\", \"FETCH_HEAD\"])).trim();\n const dirty = (await git([\"-C\", dir, \"status\", \"--porcelain\"])).trim().length > 0;\n const currentBranch = (await git([\"-C\", dir, \"rev-parse\", \"--abbrev-ref\", \"HEAD\"])).trim();\n // Clones made before the head branch name was resolvable sit on the\n // synthetic pr-<n>; treat that as the same checkout so a clean sync\n // migrates them to the real name instead of going fetch-only forever.\n const legacyPrBranch = options.prNumber !== undefined ? `pr-${options.prNumber}` : undefined;\n const onTarget =\n currentBranch === target || (legacyPrBranch !== undefined && currentBranch === legacyPrBranch);\n let localCommits = 0;\n if (onTarget) {\n // Commits unique to HEAD split two ways: ones the agent committed here\n // (the clone's preconfigured bot identity) are its work and must survive;\n // ones committed by others were force-pushed away upstream and are safe\n // to drop. Filter by the bot committer so a rebased PR head still syncs.\n const botEmail = (await git([\"-C\", dir, \"config\", \"user.email\"])).trim();\n localCommits = Number(\n (\n await git([\"-C\", dir, \"rev-list\", \"--count\", `--committer=${botEmail}`, \"FETCH_HEAD..HEAD\"])\n ).trim(),\n );\n }\n\n const updatedCheckout = onTarget && !dirty && localCommits === 0;\n if (updatedCheckout) {\n await git([\"-C\", dir, \"checkout\", \"-B\", target, \"FETCH_HEAD\"]);\n }\n return { target, fetchedSha, updatedCheckout, dirty, currentBranch, localCommits };\n}\n\n/**\n * Push one local branch to origin. Non-force by construction (plain refspec),\n * and only `pi/*` branches are accepted.\n */\nexport async function pushBranch(options: PushBranchOptions): Promise<void> {\n const { dir, branch, token } = options;\n if (!GITHUB_PUSH_BRANCH_PATTERN.test(branch)) {\n throw new Error(\n `Refusing to push branch '${branch}': only branches matching pi/<name> may be pushed.`,\n );\n }\n try {\n await git([\"-C\", dir, \"rev-parse\", \"--verify\", \"--quiet\", `refs/heads/${branch}`]);\n } catch {\n throw new Error(`Branch '${branch}' does not exist in the conversation's ./repo clone.`);\n }\n await git([\"-C\", dir, \"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`], token);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../../src/adapters/github/repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"child_process\";\nimport { join } from \"path\";\nimport { promisify } from \"util\";\nimport type {\n CloneRepoOptions,\n PushBranchOptions,\n SyncRepoOptions,\n SyncRepoResult,\n} from \"./types.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_TIMEOUT_MS = 120_000;\n\n/**\n * Branches the bot may push: `pi/<name>`. Everything else — including the\n * repo's default branch by construction — is refused, so a driven agent can\n * only ever produce reviewable branches, never touch mainline directly.\n */\nexport const GITHUB_PUSH_BRANCH_PATTERN = /^pi\\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;\n\n/** The conversation's clone location (bind-mounted into the sandbox as ./repo). */\nexport function conversationRepoDir(workingDir: string, conversationId: string): string {\n return join(workingDir, conversationId, \"repo\");\n}\n\n/**\n * Pass the ephemeral token as a per-invocation header, never in the remote\n * URL: nothing credential-shaped may end up in `.git/config`, which lives in\n * the conversation dir and is bind-mounted into the sandbox.\n */\nfunction gitAuthArgs(token: string): string[] {\n const basic = Buffer.from(`x-access-token:${token}`).toString(\"base64\");\n return [\"-c\", `http.extraheader=Authorization: basic ${basic}`];\n}\n\nasync function git(args: string[], token?: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [...(token ? gitAuthArgs(token) : []), ...args], {\n timeout: GIT_TIMEOUT_MS,\n env: { ...process.env, GIT_TERMINAL_PROMPT: \"0\" },\n });\n return stdout;\n}\n\n/**\n * Git itself rejects these ref shapes; the check just keeps an API-supplied\n * branch name that would parse as a git option (leading dash) or an invalid\n * ref out of argv entirely instead of surfacing as a git error mid-clone.\n */\nfunction isSafeBranchName(name: string): boolean {\n return (\n name.length > 0 && !name.startsWith(\"-\") && !name.includes(\"..\") && !/[\\s~^:?*[\\\\]/.test(name)\n );\n}\n\n/**\n * Local checkout name for a PR conversation: the PR's real head branch when\n * known (same-repo heads only — the caller resolves that), falling back to\n * the synthetic `pr-<n>` for fork PRs, unresolvable heads, and unsafe names.\n * Using the real name is what lets the agent update the PR in place: commit\n * on a `pi/*` head and `github_pr` pushes back to the same branch.\n */\nfunction prCheckoutName(prNumber: number, prHeadBranch?: string): string {\n return prHeadBranch && isSafeBranchName(prHeadBranch) ? prHeadBranch : `pr-${prNumber}`;\n}\n\n/**\n * Shallow-clone a repo into a conversation dir and preconfigure the bot's\n * commit identity. For PR conversations, additionally fetch the PR head and\n * check it out so the agent starts on the code under review — under its real\n * branch name when the caller resolved one, which is what lets the agent push\n * fixes back to the PR itself.\n */\nexport async function cloneRepo(options: CloneRepoOptions): Promise<void> {\n await git([\"clone\", \"--depth\", \"50\", options.url, options.dir], options.token);\n await git([\"-C\", options.dir, \"config\", \"user.name\", options.botLogin]);\n await git([\"-C\", options.dir, \"config\", \"user.email\", options.botEmail]);\n if (options.prNumber !== undefined) {\n await git(\n [\"-C\", options.dir, \"fetch\", \"--depth\", \"50\", \"origin\", `pull/${options.prNumber}/head`],\n options.token,\n );\n await git([\n \"-C\",\n options.dir,\n \"checkout\",\n \"-B\",\n prCheckoutName(options.prNumber, options.prHeadBranch),\n \"FETCH_HEAD\",\n ]);\n }\n}\n\n/**\n * Refresh a conversation clone from origin. Always fetches; only moves the\n * checkout when that provably cannot lose agent work: the target branch is\n * the one checked out, the tree is clean, and it has no commits of its own\n * (`checkout -B` then handles fast-forward and force-pushed heads alike).\n * Anything else stays fetch-only — FETCH_HEAD is left for the agent, which\n * has full credential-less git inside the sandbox, to merge or rebase itself.\n */\nexport async function syncRepo(options: SyncRepoOptions): Promise<SyncRepoResult> {\n const { dir, token } = options;\n let refspec: string;\n let target: string;\n if (options.branch) {\n refspec = options.branch;\n target = options.branch;\n } else if (options.prNumber !== undefined) {\n refspec = `pull/${options.prNumber}/head`;\n target = prCheckoutName(options.prNumber, options.prHeadBranch);\n } else if (options.defaultBranch) {\n refspec = options.defaultBranch;\n target = options.defaultBranch;\n } else {\n throw new Error(\"syncRepo needs a branch, a PR number, or the default branch\");\n }\n\n await git([\"-C\", dir, \"fetch\", \"--depth\", \"50\", \"origin\", refspec], token);\n const fetchedSha = (await git([\"-C\", dir, \"rev-parse\", \"FETCH_HEAD\"])).trim();\n const dirty = (await git([\"-C\", dir, \"status\", \"--porcelain\"])).trim().length > 0;\n const currentBranch = (await git([\"-C\", dir, \"rev-parse\", \"--abbrev-ref\", \"HEAD\"])).trim();\n // Clones made before the head branch name was resolvable sit on the\n // synthetic pr-<n>; treat that as the same checkout so a clean sync\n // migrates them to the real name instead of going fetch-only forever.\n const legacyPrBranch = options.prNumber !== undefined ? `pr-${options.prNumber}` : undefined;\n const onTarget =\n currentBranch === target || (legacyPrBranch !== undefined && currentBranch === legacyPrBranch);\n let localCommits = 0;\n if (onTarget) {\n // Commits unique to HEAD split two ways: ones the agent committed here\n // (the clone's preconfigured bot identity) are its work and must survive;\n // ones committed by others were force-pushed away upstream and are safe\n // to drop. Filter by the bot committer so a rebased PR head still syncs.\n const botEmail = (await git([\"-C\", dir, \"config\", \"user.email\"])).trim();\n localCommits = Number(\n (\n await git([\"-C\", dir, \"rev-list\", \"--count\", `--committer=${botEmail}`, \"FETCH_HEAD..HEAD\"])\n ).trim(),\n );\n }\n\n const updatedCheckout = onTarget && !dirty && localCommits === 0;\n if (updatedCheckout) {\n await git([\"-C\", dir, \"checkout\", \"-B\", target, \"FETCH_HEAD\"]);\n }\n return { target, fetchedSha, updatedCheckout, dirty, currentBranch, localCommits };\n}\n\n/**\n * Push one local branch to origin. Non-force by construction (plain refspec),\n * and only `pi/*` branches are accepted.\n */\nexport async function pushBranch(options: PushBranchOptions): Promise<void> {\n const { dir, branch, token } = options;\n if (!GITHUB_PUSH_BRANCH_PATTERN.test(branch)) {\n throw new Error(\n `Refusing to push branch '${branch}': only branches matching pi/<name> may be pushed.`,\n );\n }\n try {\n await git([\"-C\", dir, \"rev-parse\", \"--verify\", \"--quiet\", `refs/heads/${branch}`]);\n } catch {\n throw new Error(`Branch '${branch}' does not exist in the conversation's ./repo clone.`);\n }\n await git([\"-C\", dir, \"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`], token);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../../../src/adapters/github/tools/checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../../../src/adapters/github/tools/checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,QAAA,MAAM,kBAAkB;;;;EAsBtB,CAAC;AA+BH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC9D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,IAAI,EAAE,SAAS,CAAC,OAAO,kBAAkB,CAAC,CAAC;IAC3C,uBAAuB,EAAE,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;CAChE,CA+DA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { defineHostFnTool } from "../../../tools/host-fn-tool.js";
|
|
2
3
|
const githubChecksSchema = Type.Object({
|
|
3
4
|
branch: Type.Optional(Type.String({
|
|
4
5
|
description: "Branch whose CI checks to read (e.g. a pi/<name> branch you pushed). " +
|
|
@@ -45,10 +46,8 @@ function formatCheckLine(run) {
|
|
|
45
46
|
* Read-only, host-side; wired per run and only for GitHub conversations.
|
|
46
47
|
*/
|
|
47
48
|
export function createGithubChecksTool() {
|
|
48
|
-
|
|
49
|
-
const tool = {
|
|
49
|
+
const { tool, setFn } = defineHostFnTool({
|
|
50
50
|
name: "github_checks",
|
|
51
|
-
label: "github_checks",
|
|
52
51
|
description: "Read CI status (check runs) for a branch you pushed with github_pr, or for this " +
|
|
53
52
|
"conversation's pull request when branch is omitted. Pass job_id (a [job …] id, " +
|
|
54
53
|
"GitHub Actions) or build_id (a [build …] uuid, Cloud Build) from the summary to " +
|
|
@@ -56,13 +55,8 @@ export function createGithubChecksTool() {
|
|
|
56
55
|
"their summary/url, or reproduce the failure locally in ./repo. Only available in " +
|
|
57
56
|
"GitHub conversations.",
|
|
58
57
|
parameters: githubChecksSchema,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw new Error("github_checks is only available in GitHub conversations.");
|
|
62
|
-
}
|
|
63
|
-
if (signal?.aborted) {
|
|
64
|
-
throw new Error("Operation aborted");
|
|
65
|
-
}
|
|
58
|
+
unavailable: "github_checks is only available in GitHub conversations.",
|
|
59
|
+
run: async (checksFns, args) => {
|
|
66
60
|
if (args.job_id !== undefined && args.build_id !== undefined) {
|
|
67
61
|
throw new Error("Pass either job_id or build_id, not both.");
|
|
68
62
|
}
|
|
@@ -105,12 +99,7 @@ export function createGithubChecksTool() {
|
|
|
105
99
|
details: undefined,
|
|
106
100
|
};
|
|
107
101
|
},
|
|
108
|
-
};
|
|
109
|
-
return {
|
|
110
|
-
tool,
|
|
111
|
-
setGithubChecksFunction: (fns) => {
|
|
112
|
-
checksFns = fns;
|
|
113
|
-
},
|
|
114
|
-
};
|
|
102
|
+
});
|
|
103
|
+
return { tool, setGithubChecksFunction: setFn };
|
|
115
104
|
}
|
|
116
105
|
//# sourceMappingURL=checks.js.map
|