@oh-my-pi/pi-coding-agent 14.2.1 → 14.4.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/CHANGELOG.md +143 -1
- package/package.json +19 -19
- package/src/autoresearch/prompt.md +1 -1
- package/src/cli/args.ts +10 -1
- package/src/cli/shell-cli.ts +15 -3
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/config/model-registry.ts +67 -15
- package/src/config/prompt-templates.ts +5 -5
- package/src/config/settings-schema.ts +63 -4
- package/src/cursor.ts +3 -8
- package/src/debug/system-info.ts +6 -2
- package/src/discovery/claude.ts +58 -36
- package/src/discovery/helpers.ts +3 -3
- package/src/discovery/opencode.ts +20 -2
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +87 -57
- package/src/edit/line-hash.ts +735 -19
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +658 -0
- package/src/edit/modes/chunk.ts +144 -78
- package/src/edit/modes/hashline.ts +223 -146
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +112 -143
- package/src/edit/streaming.ts +385 -0
- package/src/exec/bash-executor.ts +58 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +4 -12
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/pi-protocol.ts +0 -2
- package/src/lsp/client.ts +8 -1
- package/src/lsp/defaults.json +2 -1
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/acp/acp-agent.ts +76 -2
- package/src/modes/components/assistant-message.ts +5 -34
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/settings-defs.ts +6 -1
- package/src/modes/components/todo-reminder.ts +1 -8
- package/src/modes/components/tool-execution.ts +112 -105
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +0 -2
- package/src/modes/print-mode.ts +8 -0
- package/src/modes/theme/mermaid-cache.ts +13 -52
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +4 -4
- package/src/prompts/ci-green-request.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +3 -3
- package/src/prompts/system/subagent-yield-reminder.md +11 -0
- package/src/prompts/system/system-prompt.md +4 -1
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +15 -19
- package/src/prompts/tools/ast-grep.md +18 -24
- package/src/prompts/tools/atom.md +96 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/chunk-edit.md +58 -179
- package/src/prompts/tools/debug.md +4 -5
- package/src/prompts/tools/exit-plan-mode.md +4 -5
- package/src/prompts/tools/find.md +4 -8
- package/src/prompts/tools/github.md +18 -0
- package/src/prompts/tools/grep.md +8 -8
- package/src/prompts/tools/hashline.md +22 -89
- package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
- package/src/prompts/tools/inspect-image.md +6 -6
- package/src/prompts/tools/lsp.md +6 -0
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +46 -8
- package/src/prompts/tools/read.md +9 -6
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +22 -14
- package/src/session/agent-session.ts +61 -22
- package/src/session/session-manager.ts +228 -57
- package/src/session/streaming-output.ts +11 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +44 -48
- package/src/task/render.ts +11 -13
- package/src/tools/ask.ts +7 -7
- package/src/tools/ast-edit.ts +45 -41
- package/src/tools/ast-grep.ts +77 -85
- package/src/tools/bash.ts +21 -9
- package/src/tools/browser.ts +32 -30
- package/src/tools/calculator.ts +4 -4
- package/src/tools/cancel-job.ts +1 -1
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/debug.ts +41 -37
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/gh-renderer.ts +12 -4
- package/src/tools/gh.ts +514 -712
- package/src/tools/grep.ts +115 -130
- package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
- package/src/tools/index.ts +14 -32
- package/src/tools/inspect-image.ts +3 -3
- package/src/tools/json-tree.ts +114 -114
- package/src/tools/match-line-format.ts +9 -8
- package/src/tools/notebook.ts +8 -7
- package/src/tools/poll-tool.ts +2 -1
- package/src/tools/python.ts +9 -23
- package/src/tools/read.ts +32 -21
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +18 -0
- package/src/tools/renderers.ts +2 -2
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +12 -10
- package/src/tools/search-tool-bm25.ts +2 -4
- package/src/tools/sqlite-reader.ts +116 -3
- package/src/tools/ssh.ts +4 -4
- package/src/tools/todo-write.ts +172 -147
- package/src/tools/vim.ts +14 -15
- package/src/tools/write.ts +4 -4
- package/src/tools/{submit-result.ts → yield.ts} +11 -13
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/file-display-mode.ts +10 -5
- package/src/utils/git.ts +9 -5
- package/src/utils/shell-snapshot.ts +2 -3
- package/src/vim/render.ts +4 -4
- package/src/web/search/providers/codex.ts +129 -6
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/prompts/tools/gh-issue-view.md +0 -11
- package/src/prompts/tools/gh-pr-checkout.md +0 -12
- package/src/prompts/tools/gh-pr-diff.md +0 -12
- package/src/prompts/tools/gh-pr-push.md +0 -11
- package/src/prompts/tools/gh-pr-view.md +0 -11
- package/src/prompts/tools/gh-repo-view.md +0 -11
- package/src/prompts/tools/gh-run-watch.md +0 -12
- package/src/prompts/tools/gh-search-issues.md +0 -11
- package/src/prompts/tools/gh-search-prs.md +0 -11
package/src/tools/gh.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { abortableSleep, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { type Static, Type } from "@sinclair/typebox";
|
|
6
|
-
import
|
|
7
|
-
import ghPrCheckoutDescription from "../prompts/tools/gh-pr-checkout.md" with { type: "text" };
|
|
8
|
-
import ghPrDiffDescription from "../prompts/tools/gh-pr-diff.md" with { type: "text" };
|
|
9
|
-
import ghPrPushDescription from "../prompts/tools/gh-pr-push.md" with { type: "text" };
|
|
10
|
-
import ghPrViewDescription from "../prompts/tools/gh-pr-view.md" with { type: "text" };
|
|
11
|
-
import ghRepoViewDescription from "../prompts/tools/gh-repo-view.md" with { type: "text" };
|
|
12
|
-
import ghRunWatchDescription from "../prompts/tools/gh-run-watch.md" with { type: "text" };
|
|
13
|
-
import ghSearchIssuesDescription from "../prompts/tools/gh-search-issues.md" with { type: "text" };
|
|
14
|
-
import ghSearchPrsDescription from "../prompts/tools/gh-search-prs.md" with { type: "text" };
|
|
7
|
+
import githubDescription from "../prompts/tools/github.md" with { type: "text" };
|
|
15
8
|
import * as git from "../utils/git";
|
|
16
9
|
import type { ToolSession } from ".";
|
|
17
10
|
import { formatShortSha } from "./gh-format";
|
|
@@ -135,124 +128,67 @@ const RUN_SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
|
135
128
|
const RUN_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "startup_failure"]);
|
|
136
129
|
const JOB_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required"]);
|
|
137
130
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
131
|
+
const githubSchema = Type.Object({
|
|
132
|
+
op: StringEnum(
|
|
133
|
+
[
|
|
134
|
+
"repo_view",
|
|
135
|
+
"issue_view",
|
|
136
|
+
"pr_view",
|
|
137
|
+
"pr_diff",
|
|
138
|
+
"pr_checkout",
|
|
139
|
+
"pr_push",
|
|
140
|
+
"search_issues",
|
|
141
|
+
"search_prs",
|
|
142
|
+
"run_watch",
|
|
143
|
+
],
|
|
144
|
+
{ description: "github operation" },
|
|
143
145
|
),
|
|
144
|
-
branch: Type.Optional(Type.String({ description: "Branch name to inspect instead of the default branch." })),
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const ghIssueViewSchema = Type.Object({
|
|
148
|
-
issue: Type.String({ description: "Issue number or full GitHub issue URL." }),
|
|
149
146
|
repo: Type.Optional(
|
|
150
|
-
Type.String({ description: "Repository in OWNER/REPO format. Omit when passing a full issue URL." }),
|
|
151
|
-
),
|
|
152
|
-
comments: Type.Optional(Type.Boolean({ description: "Include issue comments.", default: true })),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const ghPrViewSchema = Type.Object({
|
|
156
|
-
pr: Type.Optional(
|
|
157
147
|
Type.String({
|
|
158
|
-
description:
|
|
159
|
-
|
|
148
|
+
description: "owner/repo (any op)",
|
|
149
|
+
examples: ["facebook/react"],
|
|
160
150
|
}),
|
|
161
151
|
),
|
|
162
|
-
|
|
163
|
-
Type.String({ description: "Repository in OWNER/REPO format. Omit when passing a full pull request URL." }),
|
|
164
|
-
),
|
|
165
|
-
comments: Type.Optional(Type.Boolean({ description: "Include pull request comments.", default: true })),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const ghPrDiffSchema = Type.Object({
|
|
169
|
-
pr: Type.Optional(
|
|
152
|
+
branch: Type.Optional(
|
|
170
153
|
Type.String({
|
|
171
|
-
description:
|
|
172
|
-
|
|
173
|
-
}),
|
|
174
|
-
),
|
|
175
|
-
repo: Type.Optional(
|
|
176
|
-
Type.String({ description: "Repository in OWNER/REPO format. Omit when passing a full pull request URL." }),
|
|
177
|
-
),
|
|
178
|
-
nameOnly: Type.Optional(
|
|
179
|
-
Type.Boolean({ description: "Return only changed file names instead of unified diff output." }),
|
|
180
|
-
),
|
|
181
|
-
exclude: Type.Optional(
|
|
182
|
-
Type.Array(Type.String({ description: "Glob pattern for files to exclude from the diff." }), {
|
|
183
|
-
description: "File globs to exclude from the diff output.",
|
|
154
|
+
description: "branch (repo_view, pr_checkout local branch, pr_push local branch, run_watch)",
|
|
155
|
+
examples: ["main", "develop"],
|
|
184
156
|
}),
|
|
185
157
|
),
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const ghPrCheckoutSchema = Type.Object({
|
|
189
|
-
pr: Type.Optional(
|
|
158
|
+
issue: Type.Optional(
|
|
190
159
|
Type.String({
|
|
191
|
-
description:
|
|
192
|
-
|
|
160
|
+
description: "issue number or url (issue_view)",
|
|
161
|
+
examples: ["123", "https://github.com/owner/repo/issues/123"],
|
|
193
162
|
}),
|
|
194
163
|
),
|
|
195
|
-
|
|
196
|
-
Type.String({ description: "Repository in OWNER/REPO format. Omit when passing a full pull request URL." }),
|
|
197
|
-
),
|
|
198
|
-
branch: Type.Optional(Type.String({ description: "Local branch name to create or reuse (default: pr-<number>)." })),
|
|
199
|
-
worktree: Type.Optional(
|
|
200
|
-
Type.String({ description: "Worktree path to create. Defaults to <repo>/.worktrees/<branch>." }),
|
|
201
|
-
),
|
|
202
|
-
force: Type.Optional(
|
|
203
|
-
Type.Boolean({
|
|
204
|
-
description: "Reset an existing local branch to the PR head when it is not already checked out elsewhere.",
|
|
205
|
-
}),
|
|
206
|
-
),
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const ghPrPushSchema = Type.Object({
|
|
210
|
-
branch: Type.Optional(
|
|
164
|
+
pr: Type.Optional(
|
|
211
165
|
Type.String({
|
|
212
|
-
description: "
|
|
166
|
+
description: "pr number, url, or branch (pr_view, pr_diff, pr_checkout)",
|
|
167
|
+
examples: ["123", "feature-branch"],
|
|
213
168
|
}),
|
|
214
169
|
),
|
|
215
|
-
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
repo: Type.Optional(Type.String({ description: "Repository in OWNER/REPO format to scope the search." })),
|
|
221
|
-
limit: Type.Optional(Type.Number({ description: "Maximum results to return (max: 50).", default: 10 })),
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const ghSearchPrsSchema = Type.Object({
|
|
225
|
-
query: Type.String({ description: "GitHub pull request search query. Supports GitHub search syntax." }),
|
|
226
|
-
repo: Type.Optional(Type.String({ description: "Repository in OWNER/REPO format to scope the search." })),
|
|
227
|
-
limit: Type.Optional(Type.Number({ description: "Maximum results to return (max: 50).", default: 10 })),
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const ghRunWatchSchema = Type.Object({
|
|
231
|
-
run: Type.Optional(
|
|
232
|
-
Type.String({
|
|
233
|
-
description:
|
|
234
|
-
"GitHub Actions run ID or full run URL. Omitting this watches the workflow runs for the current HEAD commit on the selected branch.",
|
|
170
|
+
comments: Type.Optional(Type.Boolean({ description: "include comments (issue_view, pr_view)", default: true })),
|
|
171
|
+
nameOnly: Type.Optional(Type.Boolean({ description: "return file names only (pr_diff)" })),
|
|
172
|
+
exclude: Type.Optional(
|
|
173
|
+
Type.Array(Type.String({ description: "glob to exclude" }), {
|
|
174
|
+
description: "file globs to exclude (pr_diff)",
|
|
235
175
|
}),
|
|
236
176
|
),
|
|
237
|
-
|
|
177
|
+
worktree: Type.Optional(Type.String({ description: "worktree path (pr_checkout)" })),
|
|
178
|
+
force: Type.Optional(Type.Boolean({ description: "reset existing local branch (pr_checkout)" })),
|
|
179
|
+
forceWithLease: Type.Optional(Type.Boolean({ description: "force-with-lease push (pr_push)" })),
|
|
180
|
+
query: Type.Optional(
|
|
238
181
|
Type.String({
|
|
239
|
-
description: "
|
|
182
|
+
description: "search query (search_issues, search_prs)",
|
|
183
|
+
examples: ["is:open label:bug"],
|
|
240
184
|
}),
|
|
241
185
|
),
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
),
|
|
186
|
+
limit: Type.Optional(Type.Number({ description: "max results (search_issues, search_prs)", default: 10 })),
|
|
187
|
+
run: Type.Optional(Type.String({ description: "actions run id or url (run_watch)", examples: ["123456"] })),
|
|
188
|
+
tail: Type.Optional(Type.Number({ description: "log lines per failed job (run_watch)", default: 15 })),
|
|
245
189
|
});
|
|
246
190
|
|
|
247
|
-
type
|
|
248
|
-
type GhIssueViewInput = Static<typeof ghIssueViewSchema>;
|
|
249
|
-
type GhPrViewInput = Static<typeof ghPrViewSchema>;
|
|
250
|
-
type GhPrDiffInput = Static<typeof ghPrDiffSchema>;
|
|
251
|
-
type GhPrCheckoutInput = Static<typeof ghPrCheckoutSchema>;
|
|
252
|
-
type GhPrPushInput = Static<typeof ghPrPushSchema>;
|
|
253
|
-
type GhSearchIssuesInput = Static<typeof ghSearchIssuesSchema>;
|
|
254
|
-
type GhSearchPrsInput = Static<typeof ghSearchPrsSchema>;
|
|
255
|
-
type GhRunWatchInput = Static<typeof ghRunWatchSchema>;
|
|
191
|
+
type GithubInput = Static<typeof githubSchema>;
|
|
256
192
|
|
|
257
193
|
export interface GhToolDetails {
|
|
258
194
|
meta?: OutputMeta;
|
|
@@ -611,14 +547,6 @@ function toLocalBranchRef(value: string): string {
|
|
|
611
547
|
return `refs/heads/${value}`;
|
|
612
548
|
}
|
|
613
549
|
|
|
614
|
-
function stripHeadsRef(value: string | undefined): string | undefined {
|
|
615
|
-
if (!value) {
|
|
616
|
-
return undefined;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
return value.startsWith("refs/heads/") ? value.slice("refs/heads/".length) : value;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
550
|
async function requireGitRepoRoot(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
623
551
|
const repoRoot = await git.repo.root(cwd, signal);
|
|
624
552
|
if (!repoRoot) {
|
|
@@ -763,10 +691,13 @@ async function resolvePrBranchPushTarget(
|
|
|
763
691
|
maintainerCanModify?: boolean;
|
|
764
692
|
isCrossRepository: boolean;
|
|
765
693
|
}> {
|
|
694
|
+
const headRef = await git.config.getBranch(repoRoot, localBranch, "ompPrHeadRef", signal);
|
|
695
|
+
if (!headRef) {
|
|
696
|
+
throw new ToolError(`branch ${localBranch} has no PR push metadata; check it out via op: pr_checkout first`);
|
|
697
|
+
}
|
|
698
|
+
|
|
766
699
|
const pushRemote = await git.config.getBranch(repoRoot, localBranch, "pushRemote", signal);
|
|
767
700
|
const remote = await git.config.getBranch(repoRoot, localBranch, "remote", signal);
|
|
768
|
-
const mergeRef = await git.config.getBranch(repoRoot, localBranch, "merge", signal);
|
|
769
|
-
const headRef = await git.config.getBranch(repoRoot, localBranch, "ompPrHeadRef", signal);
|
|
770
701
|
const prUrl = await git.config.getBranch(repoRoot, localBranch, "ompPrUrl", signal);
|
|
771
702
|
const maintainerCanModifyValue = await git.config.getBranch(
|
|
772
703
|
repoRoot,
|
|
@@ -781,14 +712,9 @@ async function resolvePrBranchPushTarget(
|
|
|
781
712
|
throw new ToolError(`branch ${localBranch} has no configured push remote`);
|
|
782
713
|
}
|
|
783
714
|
|
|
784
|
-
const remoteBranch = headRef ?? stripHeadsRef(mergeRef);
|
|
785
|
-
if (!remoteBranch) {
|
|
786
|
-
throw new ToolError(`branch ${localBranch} has no tracked PR head ref`);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
715
|
return {
|
|
790
716
|
remoteName,
|
|
791
|
-
remoteBranch,
|
|
717
|
+
remoteBranch: headRef,
|
|
792
718
|
remoteUrl: await git.remote.url(repoRoot, remoteName, signal),
|
|
793
719
|
prUrl,
|
|
794
720
|
maintainerCanModify:
|
|
@@ -1648,7 +1574,7 @@ function formatReviewCommentsSection(comments: GhPrReviewComment[] | undefined):
|
|
|
1648
1574
|
return lines;
|
|
1649
1575
|
}
|
|
1650
1576
|
|
|
1651
|
-
function formatRepoView(data: GhRepoViewData, input:
|
|
1577
|
+
function formatRepoView(data: GhRepoViewData, input: { repo?: string; branch?: string }): string {
|
|
1652
1578
|
const lines: string[] = [];
|
|
1653
1579
|
const name = data.nameWithOwner ?? input.repo ?? "GitHub Repository";
|
|
1654
1580
|
lines.push(`# ${name}`);
|
|
@@ -1675,7 +1601,7 @@ function formatRepoView(data: GhRepoViewData, input: GhRepoViewInput): string {
|
|
|
1675
1601
|
return lines.join("\n").trim();
|
|
1676
1602
|
}
|
|
1677
1603
|
|
|
1678
|
-
function formatIssueView(data: GhIssueViewData, input:
|
|
1604
|
+
function formatIssueView(data: GhIssueViewData, input: { issue: string; repo?: string; comments?: boolean }): string {
|
|
1679
1605
|
const lines: string[] = [];
|
|
1680
1606
|
const issueNumber = data.number ?? input.issue;
|
|
1681
1607
|
lines.push(`# Issue #${issueNumber}: ${data.title ?? "Untitled"}`);
|
|
@@ -1721,7 +1647,7 @@ function formatPrFiles(files: GhPrFile[] | undefined): string[] {
|
|
|
1721
1647
|
return lines;
|
|
1722
1648
|
}
|
|
1723
1649
|
|
|
1724
|
-
function formatPrView(data: GhPrViewData, input:
|
|
1650
|
+
function formatPrView(data: GhPrViewData, input: { pr?: string; repo?: string; comments?: boolean }): string {
|
|
1725
1651
|
const lines: string[] = [];
|
|
1726
1652
|
const prIdentifier = data.number ?? input.pr ?? "current";
|
|
1727
1653
|
lines.push(`# Pull Request #${prIdentifier}: ${data.title ?? "Untitled"}`);
|
|
@@ -1891,644 +1817,520 @@ function buildTextResult(
|
|
|
1891
1817
|
return builder.done();
|
|
1892
1818
|
}
|
|
1893
1819
|
|
|
1894
|
-
export class
|
|
1895
|
-
readonly name = "
|
|
1896
|
-
readonly label = "GitHub
|
|
1897
|
-
readonly description = prompt.render(
|
|
1898
|
-
readonly parameters =
|
|
1820
|
+
export class GithubTool implements AgentTool<typeof githubSchema, GhToolDetails> {
|
|
1821
|
+
readonly name = "github";
|
|
1822
|
+
readonly label = "GitHub";
|
|
1823
|
+
readonly description = prompt.render(githubDescription);
|
|
1824
|
+
readonly parameters = githubSchema;
|
|
1899
1825
|
readonly strict = true;
|
|
1900
1826
|
|
|
1901
1827
|
constructor(private readonly session: ToolSession) {}
|
|
1902
1828
|
|
|
1903
|
-
static createIf(session: ToolSession):
|
|
1829
|
+
static createIf(session: ToolSession): GithubTool | null {
|
|
1904
1830
|
if (!git.github.available()) return null;
|
|
1905
|
-
return new
|
|
1831
|
+
return new GithubTool(session);
|
|
1906
1832
|
}
|
|
1907
1833
|
|
|
1908
1834
|
async execute(
|
|
1909
1835
|
_toolCallId: string,
|
|
1910
|
-
params:
|
|
1836
|
+
params: GithubInput,
|
|
1911
1837
|
signal?: AbortSignal,
|
|
1912
|
-
|
|
1838
|
+
onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
1913
1839
|
_context?: AgentToolContext,
|
|
1914
1840
|
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1915
1841
|
return untilAborted(signal, async () => {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1842
|
+
switch (params.op) {
|
|
1843
|
+
case "repo_view":
|
|
1844
|
+
return executeRepoView(this.session, params, signal);
|
|
1845
|
+
case "issue_view":
|
|
1846
|
+
return executeIssueView(this.session, params, signal);
|
|
1847
|
+
case "pr_view":
|
|
1848
|
+
return executePrView(this.session, params, signal);
|
|
1849
|
+
case "pr_diff":
|
|
1850
|
+
return executePrDiff(this.session, params, signal);
|
|
1851
|
+
case "pr_checkout":
|
|
1852
|
+
return executePrCheckout(this.session, params, signal);
|
|
1853
|
+
case "pr_push":
|
|
1854
|
+
return executePrPush(this.session, params, signal);
|
|
1855
|
+
case "search_issues":
|
|
1856
|
+
return executeSearchIssues(this.session, params, signal);
|
|
1857
|
+
case "search_prs":
|
|
1858
|
+
return executeSearchPrs(this.session, params, signal);
|
|
1859
|
+
case "run_watch":
|
|
1860
|
+
return executeRunWatch(this.session, this.name, params, signal, onUpdate);
|
|
1924
1861
|
}
|
|
1925
|
-
args.push("--json", GH_REPO_FIELDS.join(","));
|
|
1926
|
-
|
|
1927
|
-
const data = await git.github.json<GhRepoViewData>(this.session.cwd, args, signal, {
|
|
1928
|
-
repoProvided: Boolean(repo),
|
|
1929
|
-
});
|
|
1930
|
-
return buildTextResult(formatRepoView(data, { repo, branch }), data.url);
|
|
1931
1862
|
});
|
|
1932
1863
|
}
|
|
1933
1864
|
}
|
|
1934
1865
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1866
|
+
async function executeRepoView(
|
|
1867
|
+
session: ToolSession,
|
|
1868
|
+
params: GithubInput,
|
|
1869
|
+
signal: AbortSignal | undefined,
|
|
1870
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1871
|
+
const repo = normalizeOptionalString(params.repo);
|
|
1872
|
+
const branch = normalizeOptionalString(params.branch);
|
|
1873
|
+
const args = ["repo", "view"];
|
|
1874
|
+
if (repo) {
|
|
1875
|
+
args.push(repo);
|
|
1876
|
+
}
|
|
1877
|
+
if (branch) {
|
|
1878
|
+
args.push("--branch", branch);
|
|
1879
|
+
}
|
|
1880
|
+
args.push("--json", GH_REPO_FIELDS.join(","));
|
|
1881
|
+
|
|
1882
|
+
const data = await git.github.json<GhRepoViewData>(session.cwd, args, signal, {
|
|
1883
|
+
repoProvided: Boolean(repo),
|
|
1884
|
+
});
|
|
1885
|
+
return buildTextResult(formatRepoView(data, { repo, branch }), data.url);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async function executeIssueView(
|
|
1889
|
+
session: ToolSession,
|
|
1890
|
+
params: GithubInput,
|
|
1891
|
+
signal: AbortSignal | undefined,
|
|
1892
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1893
|
+
const issue = requireNonEmpty(params.issue, "issue");
|
|
1894
|
+
const repo = normalizeOptionalString(params.repo);
|
|
1895
|
+
const includeComments = params.comments ?? true;
|
|
1896
|
+
const args = ["issue", "view", issue];
|
|
1897
|
+
appendRepoFlag(args, repo, issue);
|
|
1898
|
+
args.push("--json", (includeComments ? GH_ISSUE_FIELDS : GH_ISSUE_FIELDS_NO_COMMENTS).join(","));
|
|
1899
|
+
|
|
1900
|
+
const data = await git.github.json<GhIssueViewData>(session.cwd, args, signal, {
|
|
1901
|
+
repoProvided: Boolean(repo),
|
|
1902
|
+
});
|
|
1903
|
+
return buildTextResult(formatIssueView(data, { issue, repo, comments: includeComments }), data.url);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
async function executePrView(
|
|
1907
|
+
session: ToolSession,
|
|
1908
|
+
params: GithubInput,
|
|
1909
|
+
signal: AbortSignal | undefined,
|
|
1910
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1911
|
+
const pr = normalizeOptionalString(params.pr);
|
|
1912
|
+
const repo = normalizeOptionalString(params.repo);
|
|
1913
|
+
const includeComments = params.comments ?? true;
|
|
1914
|
+
const args = ["pr", "view"];
|
|
1915
|
+
if (pr) {
|
|
1916
|
+
args.push(pr);
|
|
1917
|
+
}
|
|
1918
|
+
appendRepoFlag(args, repo, pr);
|
|
1919
|
+
args.push("--json", (includeComments ? GH_PR_FIELDS : GH_PR_FIELDS_NO_COMMENTS).join(","));
|
|
1920
|
+
|
|
1921
|
+
const data = await git.github.json<GhPrViewData>(session.cwd, args, signal, {
|
|
1922
|
+
repoProvided: Boolean(repo),
|
|
1923
|
+
});
|
|
1924
|
+
const resolvedRepo = repo ?? parsePullRequestUrl(data.url).repo;
|
|
1925
|
+
if (includeComments && resolvedRepo && typeof data.number === "number") {
|
|
1926
|
+
data.reviewComments = await fetchPrReviewComments(session.cwd, resolvedRepo, data.number, signal);
|
|
1927
|
+
}
|
|
1928
|
+
return buildTextResult(formatPrView(data, { pr, repo, comments: includeComments }), data.url);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
async function executePrDiff(
|
|
1932
|
+
session: ToolSession,
|
|
1933
|
+
params: GithubInput,
|
|
1934
|
+
signal: AbortSignal | undefined,
|
|
1935
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1936
|
+
const pr = normalizeOptionalString(params.pr);
|
|
1937
|
+
const repo = normalizeOptionalString(params.repo);
|
|
1938
|
+
const args = ["pr", "diff"];
|
|
1939
|
+
if (pr) {
|
|
1940
|
+
args.push(pr);
|
|
1941
|
+
}
|
|
1942
|
+
appendRepoFlag(args, repo, pr);
|
|
1943
|
+
args.push("--color", "never");
|
|
1944
|
+
if (params.nameOnly) {
|
|
1945
|
+
args.push("--name-only");
|
|
1946
|
+
}
|
|
1947
|
+
for (const pattern of params.exclude ?? []) {
|
|
1948
|
+
const normalizedPattern = requireNonEmpty(pattern, "exclude pattern");
|
|
1949
|
+
args.push("--exclude", normalizedPattern);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
const output = await git.github.text(session.cwd, args, signal, {
|
|
1953
|
+
repoProvided: Boolean(repo),
|
|
1954
|
+
trimOutput: false,
|
|
1955
|
+
});
|
|
1956
|
+
const title = params.nameOnly ? "# Pull Request Files" : "# Pull Request Diff";
|
|
1957
|
+
const body = output.length > 0 ? output : params.nameOnly ? "No changed files." : "No diff output.";
|
|
1958
|
+
return buildTextResult(`${title}\n\n${body}`);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
async function executePrCheckout(
|
|
1962
|
+
session: ToolSession,
|
|
1963
|
+
params: GithubInput,
|
|
1964
|
+
signal: AbortSignal | undefined,
|
|
1965
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1966
|
+
const pr = normalizeOptionalString(params.pr);
|
|
1967
|
+
const repo = normalizeOptionalString(params.repo);
|
|
1968
|
+
const requestedBranch = normalizeOptionalString(params.branch);
|
|
1969
|
+
const requestedWorktree = normalizeOptionalString(params.worktree);
|
|
1970
|
+
const force = params.force ?? false;
|
|
1971
|
+
const args = ["pr", "view"];
|
|
1972
|
+
if (pr) {
|
|
1973
|
+
args.push(pr);
|
|
1974
|
+
}
|
|
1975
|
+
appendRepoFlag(args, repo, pr);
|
|
1976
|
+
args.push("--json", GH_PR_CHECKOUT_FIELDS.join(","));
|
|
1977
|
+
|
|
1978
|
+
const data = await git.github.json<GhPrViewData>(session.cwd, args, signal, {
|
|
1979
|
+
repoProvided: Boolean(repo),
|
|
1980
|
+
});
|
|
1981
|
+
const prNumber = data.number;
|
|
1982
|
+
if (typeof prNumber !== "number") {
|
|
1983
|
+
throw new ToolError("GitHub CLI did not return a pull request number.");
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
const headRefName = requireNonEmpty(data.headRefName, "head branch");
|
|
1987
|
+
const headRefOid = requireNonEmpty(data.headRefOid, "head commit");
|
|
1988
|
+
const repoRoot = await requireGitRepoRoot(session.cwd, signal);
|
|
1989
|
+
const primaryRepoRoot = await requirePrimaryGitRepoRoot(repoRoot, signal);
|
|
1990
|
+
const localBranch = requestedBranch ?? `pr-${prNumber}`;
|
|
1991
|
+
const worktreePath = requestedWorktree
|
|
1992
|
+
? path.resolve(session.cwd, requestedWorktree)
|
|
1993
|
+
: path.join(primaryRepoRoot, ".worktrees", localBranch);
|
|
1994
|
+
const existingWorktrees = await git.worktree.list(repoRoot, signal);
|
|
1995
|
+
const existingWorktree = existingWorktrees.find(entry => entry.branch === toLocalBranchRef(localBranch));
|
|
1996
|
+
|
|
1997
|
+
const remote = await ensurePrRemote(repoRoot, data, signal);
|
|
1998
|
+
await git.fetch(
|
|
1999
|
+
repoRoot,
|
|
2000
|
+
remote.name,
|
|
2001
|
+
`refs/heads/${headRefName}`,
|
|
2002
|
+
`refs/remotes/${remote.name}/${headRefName}`,
|
|
2003
|
+
signal,
|
|
2004
|
+
);
|
|
1985
2005
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
const args = ["pr", "view"];
|
|
1998
|
-
if (pr) {
|
|
1999
|
-
args.push(pr);
|
|
2000
|
-
}
|
|
2001
|
-
appendRepoFlag(args, repo, pr);
|
|
2002
|
-
args.push("--json", (includeComments ? GH_PR_FIELDS : GH_PR_FIELDS_NO_COMMENTS).join(","));
|
|
2006
|
+
if (!existingWorktree) {
|
|
2007
|
+
const localBranchRef = toLocalBranchRef(localBranch);
|
|
2008
|
+
const localBranchExists = await git.ref.exists(repoRoot, localBranchRef, signal);
|
|
2009
|
+
if (localBranchExists) {
|
|
2010
|
+
const existingOid = await git.ref.resolve(repoRoot, localBranchRef, signal);
|
|
2011
|
+
if (existingOid !== headRefOid) {
|
|
2012
|
+
if (!force) {
|
|
2013
|
+
throw new ToolError(
|
|
2014
|
+
`local branch ${localBranch} already exists at ${formatShortSha(existingOid ?? undefined) ?? existingOid ?? "unknown commit"}; pass force=true to reset it`,
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2003
2017
|
|
|
2004
|
-
|
|
2005
|
-
repoProvided: Boolean(repo),
|
|
2006
|
-
});
|
|
2007
|
-
const resolvedRepo = repo ?? parsePullRequestUrl(data.url).repo;
|
|
2008
|
-
if (includeComments && resolvedRepo && typeof data.number === "number") {
|
|
2009
|
-
data.reviewComments = await fetchPrReviewComments(this.session.cwd, resolvedRepo, data.number, signal);
|
|
2018
|
+
await git.branch.force(repoRoot, localBranch, `refs/remotes/${remote.name}/${headRefName}`, signal);
|
|
2010
2019
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
export class GhPrDiffTool implements AgentTool<typeof ghPrDiffSchema, GhToolDetails> {
|
|
2017
|
-
readonly name = "gh_pr_diff";
|
|
2018
|
-
readonly label = "GitHub PR Diff";
|
|
2019
|
-
readonly description = prompt.render(ghPrDiffDescription);
|
|
2020
|
-
readonly parameters = ghPrDiffSchema;
|
|
2021
|
-
readonly strict = true;
|
|
2022
|
-
|
|
2023
|
-
constructor(private readonly session: ToolSession) {}
|
|
2024
|
-
|
|
2025
|
-
static createIf(session: ToolSession): GhPrDiffTool | null {
|
|
2026
|
-
if (!git.github.available()) return null;
|
|
2027
|
-
return new GhPrDiffTool(session);
|
|
2020
|
+
} else {
|
|
2021
|
+
await git.branch.create(repoRoot, localBranch, `refs/remotes/${remote.name}/${headRefName}`, signal);
|
|
2022
|
+
}
|
|
2028
2023
|
}
|
|
2029
2024
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
for (const pattern of params.exclude ?? []) {
|
|
2050
|
-
const normalizedPattern = requireNonEmpty(pattern, "exclude pattern");
|
|
2051
|
-
args.push("--exclude", normalizedPattern);
|
|
2052
|
-
}
|
|
2025
|
+
await git.config.setBranch(repoRoot, localBranch, "remote", remote.name, signal);
|
|
2026
|
+
await git.config.setBranch(repoRoot, localBranch, "merge", `refs/heads/${headRefName}`, signal);
|
|
2027
|
+
await git.config.setBranch(repoRoot, localBranch, "pushRemote", remote.name, signal);
|
|
2028
|
+
await git.config.setBranch(repoRoot, localBranch, "ompPrHeadRef", headRefName, signal);
|
|
2029
|
+
await git.config.setBranch(repoRoot, localBranch, "ompPrUrl", data.url ?? "", signal);
|
|
2030
|
+
await git.config.setBranch(
|
|
2031
|
+
repoRoot,
|
|
2032
|
+
localBranch,
|
|
2033
|
+
"ompPrIsCrossRepository",
|
|
2034
|
+
String(Boolean(data.isCrossRepository)),
|
|
2035
|
+
signal,
|
|
2036
|
+
);
|
|
2037
|
+
await git.config.setBranch(
|
|
2038
|
+
repoRoot,
|
|
2039
|
+
localBranch,
|
|
2040
|
+
"ompPrMaintainerCanModify",
|
|
2041
|
+
String(Boolean(data.maintainerCanModify)),
|
|
2042
|
+
signal,
|
|
2043
|
+
);
|
|
2053
2044
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2045
|
+
const finalWorktreePath = existingWorktree?.path ?? worktreePath;
|
|
2046
|
+
if (!existingWorktree) {
|
|
2047
|
+
await ensureGitWorktreePathAvailable(finalWorktreePath, existingWorktrees);
|
|
2048
|
+
await fs.mkdir(path.dirname(finalWorktreePath), { recursive: true });
|
|
2049
|
+
await git.worktree.add(repoRoot, finalWorktreePath, localBranch, { signal });
|
|
2050
|
+
}
|
|
2051
|
+
const resolvedWorktreePath = await fs.realpath(finalWorktreePath);
|
|
2052
|
+
|
|
2053
|
+
return buildTextResult(
|
|
2054
|
+
formatPrCheckoutResult({
|
|
2055
|
+
data,
|
|
2056
|
+
localBranch,
|
|
2057
|
+
worktreePath: resolvedWorktreePath,
|
|
2058
|
+
remoteName: remote.name,
|
|
2059
|
+
remoteUrl: remote.url,
|
|
2060
|
+
reused: Boolean(existingWorktree),
|
|
2061
|
+
}),
|
|
2062
|
+
data.url,
|
|
2063
|
+
{
|
|
2064
|
+
repo: repo ?? data.headRepository?.nameWithOwner,
|
|
2065
|
+
branch: localBranch,
|
|
2066
|
+
worktreePath: resolvedWorktreePath,
|
|
2067
|
+
remote: remote.name,
|
|
2068
|
+
remoteBranch: headRefName,
|
|
2069
|
+
},
|
|
2070
|
+
);
|
|
2063
2071
|
}
|
|
2064
2072
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2073
|
+
async function executePrPush(
|
|
2074
|
+
session: ToolSession,
|
|
2075
|
+
params: GithubInput,
|
|
2076
|
+
signal: AbortSignal | undefined,
|
|
2077
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2078
|
+
const repoRoot = await requireGitRepoRoot(session.cwd, signal);
|
|
2079
|
+
const localBranch = normalizeOptionalString(params.branch) ?? (await requireCurrentGitBranch(repoRoot, signal));
|
|
2080
|
+
const refExists = await git.ref.exists(repoRoot, toLocalBranchRef(localBranch), signal);
|
|
2081
|
+
if (!refExists) {
|
|
2082
|
+
throw new ToolError(`local branch ${localBranch} does not exist`);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
const target = await resolvePrBranchPushTarget(repoRoot, localBranch, signal);
|
|
2086
|
+
const currentBranch = await git.branch.current(repoRoot, signal);
|
|
2087
|
+
const sourceRef = currentBranch === localBranch ? "HEAD" : toLocalBranchRef(localBranch);
|
|
2088
|
+
const refspec = `${sourceRef}:refs/heads/${target.remoteBranch}`;
|
|
2089
|
+
await git.push(repoRoot, {
|
|
2090
|
+
forceWithLease: params.forceWithLease,
|
|
2091
|
+
refspec,
|
|
2092
|
+
remote: target.remoteName,
|
|
2093
|
+
signal,
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
return buildTextResult(
|
|
2097
|
+
formatPrPushResult({
|
|
2098
|
+
localBranch,
|
|
2099
|
+
remoteName: target.remoteName,
|
|
2100
|
+
remoteBranch: target.remoteBranch,
|
|
2101
|
+
remoteUrl: target.remoteUrl,
|
|
2102
|
+
prUrl: target.prUrl,
|
|
2103
|
+
forceWithLease: params.forceWithLease ?? false,
|
|
2104
|
+
}),
|
|
2105
|
+
target.prUrl,
|
|
2106
|
+
{
|
|
2107
|
+
branch: localBranch,
|
|
2108
|
+
remote: target.remoteName,
|
|
2109
|
+
remoteBranch: target.remoteBranch,
|
|
2110
|
+
},
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2098
2113
|
|
|
2099
|
-
|
|
2100
|
-
|
|
2114
|
+
async function executeSearchIssues(
|
|
2115
|
+
session: ToolSession,
|
|
2116
|
+
params: GithubInput,
|
|
2117
|
+
signal: AbortSignal | undefined,
|
|
2118
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2119
|
+
const query = requireNonEmpty(params.query, "query");
|
|
2120
|
+
const repo = normalizeOptionalString(params.repo);
|
|
2121
|
+
const limit = resolveSearchLimit(params.limit);
|
|
2122
|
+
const args = buildGhSearchArgs("issues", query, limit, repo);
|
|
2123
|
+
|
|
2124
|
+
const items = await git.github.json<GhSearchResult[]>(session.cwd, args, signal, {
|
|
2125
|
+
repoProvided: Boolean(repo),
|
|
2126
|
+
});
|
|
2127
|
+
return buildTextResult(formatSearchResults("issues", query, repo, items));
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
async function executeSearchPrs(
|
|
2131
|
+
session: ToolSession,
|
|
2132
|
+
params: GithubInput,
|
|
2133
|
+
signal: AbortSignal | undefined,
|
|
2134
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2135
|
+
const query = requireNonEmpty(params.query, "query");
|
|
2136
|
+
const repo = normalizeOptionalString(params.repo);
|
|
2137
|
+
const limit = resolveSearchLimit(params.limit);
|
|
2138
|
+
const args = buildGhSearchArgs("prs", query, limit, repo);
|
|
2139
|
+
|
|
2140
|
+
const items = await git.github.json<GhSearchResult[]>(session.cwd, args, signal, {
|
|
2141
|
+
repoProvided: Boolean(repo),
|
|
2142
|
+
});
|
|
2143
|
+
return buildTextResult(formatSearchResults("pull requests", query, repo, items));
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
async function executeRunWatch(
|
|
2147
|
+
session: ToolSession,
|
|
2148
|
+
toolName: string,
|
|
2149
|
+
params: GithubInput,
|
|
2150
|
+
signal: AbortSignal | undefined,
|
|
2151
|
+
onUpdate: AgentToolUpdateCallback<GhToolDetails> | undefined,
|
|
2152
|
+
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2153
|
+
const branchInput = normalizeOptionalString(params.branch);
|
|
2154
|
+
const runReference = parseRunReference(params.run);
|
|
2155
|
+
const repo = await resolveGitHubRepo(session.cwd, undefined, runReference.repo, signal);
|
|
2156
|
+
const intervalSeconds = RUN_WATCH_INTERVAL_DEFAULT;
|
|
2157
|
+
const graceSeconds = RUN_WATCH_GRACE_DEFAULT;
|
|
2158
|
+
const tail = resolveTailLimit(params.tail);
|
|
2159
|
+
if (runReference.runId !== undefined) {
|
|
2160
|
+
const runId = runReference.runId;
|
|
2161
|
+
let pollCount = 0;
|
|
2162
|
+
|
|
2163
|
+
while (true) {
|
|
2164
|
+
throwIfAborted(signal);
|
|
2165
|
+
pollCount += 1;
|
|
2166
|
+
|
|
2167
|
+
let run = await fetchRunSnapshot(session.cwd, repo, runId, signal);
|
|
2168
|
+
const details = buildRunWatchDetails(repo, run, {
|
|
2169
|
+
state: "watching",
|
|
2170
|
+
pollCount,
|
|
2171
|
+
});
|
|
2172
|
+
onUpdate?.({
|
|
2173
|
+
content: [{ type: "text", text: formatRunWatchSnapshot(repo, run, pollCount) }],
|
|
2174
|
+
details,
|
|
2101
2175
|
});
|
|
2102
|
-
const prNumber = data.number;
|
|
2103
|
-
if (typeof prNumber !== "number") {
|
|
2104
|
-
throw new ToolError("GitHub CLI did not return a pull request number.");
|
|
2105
|
-
}
|
|
2106
2176
|
|
|
2107
|
-
const
|
|
2108
|
-
const
|
|
2109
|
-
const repoRoot = await requireGitRepoRoot(this.session.cwd, signal);
|
|
2110
|
-
const primaryRepoRoot = await requirePrimaryGitRepoRoot(repoRoot, signal);
|
|
2111
|
-
const localBranch = requestedBranch ?? `pr-${prNumber}`;
|
|
2112
|
-
const worktreePath = requestedWorktree
|
|
2113
|
-
? path.resolve(this.session.cwd, requestedWorktree)
|
|
2114
|
-
: path.join(primaryRepoRoot, ".worktrees", localBranch);
|
|
2115
|
-
const existingWorktrees = await git.worktree.list(repoRoot, signal);
|
|
2116
|
-
const existingWorktree = existingWorktrees.find(entry => entry.branch === toLocalBranchRef(localBranch));
|
|
2117
|
-
|
|
2118
|
-
const remote = await ensurePrRemote(repoRoot, data, signal);
|
|
2119
|
-
await git.fetch(
|
|
2120
|
-
repoRoot,
|
|
2121
|
-
remote.name,
|
|
2122
|
-
`refs/heads/${headRefName}`,
|
|
2123
|
-
`refs/remotes/${remote.name}/${headRefName}`,
|
|
2124
|
-
signal,
|
|
2125
|
-
);
|
|
2177
|
+
const failedJobs = run.jobs.filter(isFailedJob);
|
|
2178
|
+
const runCompleted = run.status === "completed";
|
|
2126
2179
|
|
|
2127
|
-
if (
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2180
|
+
if (failedJobs.length > 0) {
|
|
2181
|
+
if (!runCompleted && graceSeconds > 0) {
|
|
2182
|
+
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2183
|
+
onUpdate?.({
|
|
2184
|
+
content: [
|
|
2185
|
+
{
|
|
2186
|
+
type: "text",
|
|
2187
|
+
text: formatRunWatchSnapshot(repo, run, pollCount, note),
|
|
2188
|
+
},
|
|
2189
|
+
],
|
|
2190
|
+
details: buildRunWatchDetails(repo, run, {
|
|
2191
|
+
state: "watching",
|
|
2192
|
+
pollCount,
|
|
2193
|
+
note,
|
|
2194
|
+
}),
|
|
2195
|
+
});
|
|
2196
|
+
await abortableSleep(graceSeconds * 1000, signal);
|
|
2197
|
+
run = await fetchRunSnapshot(session.cwd, repo, runId, signal);
|
|
2143
2198
|
}
|
|
2144
|
-
}
|
|
2145
2199
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
await ensureGitWorktreePathAvailable(finalWorktreePath, existingWorktrees);
|
|
2169
|
-
await fs.mkdir(path.dirname(finalWorktreePath), { recursive: true });
|
|
2170
|
-
await git.worktree.add(repoRoot, finalWorktreePath, localBranch, { signal });
|
|
2200
|
+
const failedJobLogs = await fetchFailedJobLogs(
|
|
2201
|
+
session.cwd,
|
|
2202
|
+
repo,
|
|
2203
|
+
run.jobs.filter(isFailedJob).map(job => ({ run, job })),
|
|
2204
|
+
tail,
|
|
2205
|
+
signal,
|
|
2206
|
+
);
|
|
2207
|
+
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2208
|
+
state: "completed",
|
|
2209
|
+
failedJobLogs,
|
|
2210
|
+
});
|
|
2211
|
+
const artifactId = await saveArtifactText(
|
|
2212
|
+
session,
|
|
2213
|
+
toolName,
|
|
2214
|
+
formatRunWatchResult(repo, run, failedJobLogs, tail, { mode: "full" }),
|
|
2215
|
+
);
|
|
2216
|
+
return buildTextResult(
|
|
2217
|
+
formatRunWatchResult(repo, run, failedJobLogs, tail),
|
|
2218
|
+
run.url,
|
|
2219
|
+
{ ...finalDetails, artifactId },
|
|
2220
|
+
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2221
|
+
);
|
|
2171
2222
|
}
|
|
2172
|
-
const resolvedWorktreePath = await fs.realpath(finalWorktreePath);
|
|
2173
|
-
|
|
2174
|
-
return buildTextResult(
|
|
2175
|
-
formatPrCheckoutResult({
|
|
2176
|
-
data,
|
|
2177
|
-
localBranch,
|
|
2178
|
-
worktreePath: resolvedWorktreePath,
|
|
2179
|
-
remoteName: remote.name,
|
|
2180
|
-
remoteUrl: remote.url,
|
|
2181
|
-
reused: Boolean(existingWorktree),
|
|
2182
|
-
}),
|
|
2183
|
-
data.url,
|
|
2184
|
-
{
|
|
2185
|
-
repo: repo ?? data.headRepository?.nameWithOwner,
|
|
2186
|
-
branch: localBranch,
|
|
2187
|
-
worktreePath: resolvedWorktreePath,
|
|
2188
|
-
remote: remote.name,
|
|
2189
|
-
remoteBranch: headRefName,
|
|
2190
|
-
},
|
|
2191
|
-
);
|
|
2192
|
-
});
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
export class GhPrPushTool implements AgentTool<typeof ghPrPushSchema, GhToolDetails> {
|
|
2197
|
-
readonly name = "gh_pr_push";
|
|
2198
|
-
readonly label = "GitHub PR Push";
|
|
2199
|
-
readonly description = prompt.render(ghPrPushDescription);
|
|
2200
|
-
readonly parameters = ghPrPushSchema;
|
|
2201
|
-
readonly strict = true;
|
|
2202
|
-
|
|
2203
|
-
constructor(private readonly session: ToolSession) {}
|
|
2204
2223
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
async execute(
|
|
2211
|
-
_toolCallId: string,
|
|
2212
|
-
params: GhPrPushInput,
|
|
2213
|
-
signal?: AbortSignal,
|
|
2214
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2215
|
-
_context?: AgentToolContext,
|
|
2216
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2217
|
-
return untilAborted(signal, async () => {
|
|
2218
|
-
const repoRoot = await requireGitRepoRoot(this.session.cwd, signal);
|
|
2219
|
-
const localBranch =
|
|
2220
|
-
normalizeOptionalString(params.branch) ?? (await requireCurrentGitBranch(repoRoot, signal));
|
|
2221
|
-
const refExists = await git.ref.exists(repoRoot, toLocalBranchRef(localBranch), signal);
|
|
2222
|
-
if (!refExists) {
|
|
2223
|
-
throw new ToolError(`local branch ${localBranch} does not exist`);
|
|
2224
|
+
if (runCompleted) {
|
|
2225
|
+
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2226
|
+
state: "completed",
|
|
2227
|
+
});
|
|
2228
|
+
return buildTextResult(formatRunWatchResult(repo, run, [], tail), run.url, finalDetails);
|
|
2224
2229
|
}
|
|
2225
2230
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
const sourceRef = currentBranch === localBranch ? "HEAD" : toLocalBranchRef(localBranch);
|
|
2229
|
-
const refspec = `${sourceRef}:refs/heads/${target.remoteBranch}`;
|
|
2230
|
-
await git.push(repoRoot, {
|
|
2231
|
-
forceWithLease: params.forceWithLease,
|
|
2232
|
-
refspec,
|
|
2233
|
-
remote: target.remoteName,
|
|
2234
|
-
signal,
|
|
2235
|
-
});
|
|
2236
|
-
|
|
2237
|
-
return buildTextResult(
|
|
2238
|
-
formatPrPushResult({
|
|
2239
|
-
localBranch,
|
|
2240
|
-
remoteName: target.remoteName,
|
|
2241
|
-
remoteBranch: target.remoteBranch,
|
|
2242
|
-
remoteUrl: target.remoteUrl,
|
|
2243
|
-
prUrl: target.prUrl,
|
|
2244
|
-
forceWithLease: params.forceWithLease ?? false,
|
|
2245
|
-
}),
|
|
2246
|
-
target.prUrl,
|
|
2247
|
-
{
|
|
2248
|
-
branch: localBranch,
|
|
2249
|
-
remote: target.remoteName,
|
|
2250
|
-
remoteBranch: target.remoteBranch,
|
|
2251
|
-
},
|
|
2252
|
-
);
|
|
2253
|
-
});
|
|
2231
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2232
|
+
}
|
|
2254
2233
|
}
|
|
2255
|
-
}
|
|
2256
2234
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
constructor(private readonly session: ToolSession) {}
|
|
2235
|
+
const branch = branchInput ?? (await requireCurrentGitBranch(session.cwd, signal));
|
|
2236
|
+
const headSha = branchInput
|
|
2237
|
+
? await resolveGitHubBranchHead(session.cwd, repo, branch, signal)
|
|
2238
|
+
: await requireCurrentGitHead(session.cwd, signal);
|
|
2239
|
+
let pollCount = 0;
|
|
2240
|
+
let settledSuccessSignature: string | undefined;
|
|
2265
2241
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
async execute(
|
|
2272
|
-
_toolCallId: string,
|
|
2273
|
-
params: GhSearchIssuesInput,
|
|
2274
|
-
signal?: AbortSignal,
|
|
2275
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2276
|
-
_context?: AgentToolContext,
|
|
2277
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2278
|
-
return untilAborted(signal, async () => {
|
|
2279
|
-
const query = requireNonEmpty(params.query, "query");
|
|
2280
|
-
const repo = normalizeOptionalString(params.repo);
|
|
2281
|
-
const limit = resolveSearchLimit(params.limit);
|
|
2282
|
-
const args = buildGhSearchArgs("issues", query, limit, repo);
|
|
2242
|
+
while (true) {
|
|
2243
|
+
throwIfAborted(signal);
|
|
2244
|
+
pollCount += 1;
|
|
2283
2245
|
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2246
|
+
let runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
|
|
2247
|
+
const details = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2248
|
+
state: "watching",
|
|
2249
|
+
pollCount,
|
|
2288
2250
|
});
|
|
2289
|
-
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
export class GhSearchPrsTool implements AgentTool<typeof ghSearchPrsSchema, GhToolDetails> {
|
|
2293
|
-
readonly name = "gh_search_prs";
|
|
2294
|
-
readonly label = "GitHub PR Search";
|
|
2295
|
-
readonly description = prompt.render(ghSearchPrsDescription);
|
|
2296
|
-
readonly parameters = ghSearchPrsSchema;
|
|
2297
|
-
readonly strict = true;
|
|
2298
|
-
|
|
2299
|
-
constructor(private readonly session: ToolSession) {}
|
|
2300
|
-
|
|
2301
|
-
static createIf(session: ToolSession): GhSearchPrsTool | null {
|
|
2302
|
-
if (!git.github.available()) return null;
|
|
2303
|
-
return new GhSearchPrsTool(session);
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
async execute(
|
|
2307
|
-
_toolCallId: string,
|
|
2308
|
-
params: GhSearchPrsInput,
|
|
2309
|
-
signal?: AbortSignal,
|
|
2310
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2311
|
-
_context?: AgentToolContext,
|
|
2312
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2313
|
-
return untilAborted(signal, async () => {
|
|
2314
|
-
const query = requireNonEmpty(params.query, "query");
|
|
2315
|
-
const repo = normalizeOptionalString(params.repo);
|
|
2316
|
-
const limit = resolveSearchLimit(params.limit);
|
|
2317
|
-
const args = buildGhSearchArgs("prs", query, limit, repo);
|
|
2318
|
-
|
|
2319
|
-
const items = await git.github.json<GhSearchResult[]>(this.session.cwd, args, signal, {
|
|
2320
|
-
repoProvided: Boolean(repo),
|
|
2321
|
-
});
|
|
2322
|
-
return buildTextResult(formatSearchResults("pull requests", query, repo, items));
|
|
2251
|
+
onUpdate?.({
|
|
2252
|
+
content: [{ type: "text", text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount) }],
|
|
2253
|
+
details,
|
|
2323
2254
|
});
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
export class GhRunWatchTool implements AgentTool<typeof ghRunWatchSchema, GhToolDetails> {
|
|
2328
|
-
readonly name = "gh_run_watch";
|
|
2329
|
-
readonly label = "GitHub Run Watch";
|
|
2330
|
-
readonly description = prompt.render(ghRunWatchDescription);
|
|
2331
|
-
readonly parameters = ghRunWatchSchema;
|
|
2332
|
-
readonly strict = true;
|
|
2333
2255
|
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
_context?: AgentToolContext,
|
|
2347
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2348
|
-
return untilAborted(signal, async () => {
|
|
2349
|
-
const branchInput = normalizeOptionalString(params.branch);
|
|
2350
|
-
const runReference = parseRunReference(params.run);
|
|
2351
|
-
const repo = await resolveGitHubRepo(this.session.cwd, undefined, runReference.repo, signal);
|
|
2352
|
-
const intervalSeconds = RUN_WATCH_INTERVAL_DEFAULT;
|
|
2353
|
-
const graceSeconds = RUN_WATCH_GRACE_DEFAULT;
|
|
2354
|
-
const tail = resolveTailLimit(params.tail);
|
|
2355
|
-
if (runReference.runId !== undefined) {
|
|
2356
|
-
const runId = runReference.runId;
|
|
2357
|
-
let pollCount = 0;
|
|
2358
|
-
|
|
2359
|
-
while (true) {
|
|
2360
|
-
throwIfAborted(signal);
|
|
2361
|
-
pollCount += 1;
|
|
2362
|
-
|
|
2363
|
-
let run = await fetchRunSnapshot(this.session.cwd, repo, runId, signal);
|
|
2364
|
-
const details = buildRunWatchDetails(repo, run, {
|
|
2256
|
+
const outcome = getRunCollectionOutcome(runs);
|
|
2257
|
+
if (outcome === "failure") {
|
|
2258
|
+
if (graceSeconds > 0) {
|
|
2259
|
+
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2260
|
+
onUpdate?.({
|
|
2261
|
+
content: [
|
|
2262
|
+
{
|
|
2263
|
+
type: "text",
|
|
2264
|
+
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2265
|
+
},
|
|
2266
|
+
],
|
|
2267
|
+
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2365
2268
|
state: "watching",
|
|
2366
2269
|
pollCount,
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const failedJobs = run.jobs.filter(isFailedJob);
|
|
2374
|
-
const runCompleted = run.status === "completed";
|
|
2375
|
-
|
|
2376
|
-
if (failedJobs.length > 0) {
|
|
2377
|
-
if (!runCompleted && graceSeconds > 0) {
|
|
2378
|
-
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2379
|
-
onUpdate?.({
|
|
2380
|
-
content: [
|
|
2381
|
-
{
|
|
2382
|
-
type: "text",
|
|
2383
|
-
text: formatRunWatchSnapshot(repo, run, pollCount, note),
|
|
2384
|
-
},
|
|
2385
|
-
],
|
|
2386
|
-
details: buildRunWatchDetails(repo, run, {
|
|
2387
|
-
state: "watching",
|
|
2388
|
-
pollCount,
|
|
2389
|
-
note,
|
|
2390
|
-
}),
|
|
2391
|
-
});
|
|
2392
|
-
await abortableSleep(graceSeconds * 1000, signal);
|
|
2393
|
-
run = await fetchRunSnapshot(this.session.cwd, repo, runId, signal);
|
|
2394
|
-
}
|
|
2395
|
-
|
|
2396
|
-
const failedJobLogs = await fetchFailedJobLogs(
|
|
2397
|
-
this.session.cwd,
|
|
2398
|
-
repo,
|
|
2399
|
-
run.jobs.filter(isFailedJob).map(job => ({ run, job })),
|
|
2400
|
-
tail,
|
|
2401
|
-
signal,
|
|
2402
|
-
);
|
|
2403
|
-
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2404
|
-
state: "completed",
|
|
2405
|
-
failedJobLogs,
|
|
2406
|
-
});
|
|
2407
|
-
const artifactId = await saveArtifactText(
|
|
2408
|
-
this.session,
|
|
2409
|
-
this.name,
|
|
2410
|
-
formatRunWatchResult(repo, run, failedJobLogs, tail, { mode: "full" }),
|
|
2411
|
-
);
|
|
2412
|
-
return buildTextResult(
|
|
2413
|
-
formatRunWatchResult(repo, run, failedJobLogs, tail),
|
|
2414
|
-
run.url,
|
|
2415
|
-
{ ...finalDetails, artifactId },
|
|
2416
|
-
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2417
|
-
);
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
if (runCompleted) {
|
|
2421
|
-
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2422
|
-
state: "completed",
|
|
2423
|
-
});
|
|
2424
|
-
return buildTextResult(formatRunWatchResult(repo, run, [], tail), run.url, finalDetails);
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2428
|
-
}
|
|
2270
|
+
note,
|
|
2271
|
+
}),
|
|
2272
|
+
});
|
|
2273
|
+
await abortableSleep(graceSeconds * 1000, signal);
|
|
2274
|
+
runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
|
|
2429
2275
|
}
|
|
2430
2276
|
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2277
|
+
const failedJobLogs = await fetchFailedJobLogs(
|
|
2278
|
+
session.cwd,
|
|
2279
|
+
repo,
|
|
2280
|
+
runs.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job }))),
|
|
2281
|
+
tail,
|
|
2282
|
+
signal,
|
|
2283
|
+
);
|
|
2284
|
+
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2285
|
+
state: "completed",
|
|
2286
|
+
failedJobLogs,
|
|
2287
|
+
});
|
|
2288
|
+
const artifactId = await saveArtifactText(
|
|
2289
|
+
session,
|
|
2290
|
+
toolName,
|
|
2291
|
+
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail, { mode: "full" }),
|
|
2292
|
+
);
|
|
2293
|
+
return buildTextResult(
|
|
2294
|
+
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail),
|
|
2295
|
+
undefined,
|
|
2296
|
+
{ ...finalDetails, artifactId },
|
|
2297
|
+
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2437
2300
|
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2301
|
+
if (outcome === "success") {
|
|
2302
|
+
const signature = getRunCollectionSignature(runs);
|
|
2303
|
+
if (signature === settledSuccessSignature) {
|
|
2304
|
+
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2305
|
+
state: "completed",
|
|
2306
|
+
});
|
|
2307
|
+
return buildTextResult(
|
|
2308
|
+
formatCommitRunWatchResult(repo, headSha, branch, runs, [], tail),
|
|
2309
|
+
undefined,
|
|
2310
|
+
finalDetails,
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2441
2313
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2314
|
+
settledSuccessSignature = signature;
|
|
2315
|
+
const note = `All known workflow runs completed successfully. Waiting ${intervalSeconds}s to ensure no additional runs appear for this commit.`;
|
|
2316
|
+
onUpdate?.({
|
|
2317
|
+
content: [
|
|
2318
|
+
{
|
|
2319
|
+
type: "text",
|
|
2320
|
+
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2321
|
+
},
|
|
2322
|
+
],
|
|
2323
|
+
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2444
2324
|
state: "watching",
|
|
2445
2325
|
pollCount,
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
const outcome = getRunCollectionOutcome(runs);
|
|
2453
|
-
if (outcome === "failure") {
|
|
2454
|
-
if (graceSeconds > 0) {
|
|
2455
|
-
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2456
|
-
onUpdate?.({
|
|
2457
|
-
content: [
|
|
2458
|
-
{
|
|
2459
|
-
type: "text",
|
|
2460
|
-
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2461
|
-
},
|
|
2462
|
-
],
|
|
2463
|
-
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2464
|
-
state: "watching",
|
|
2465
|
-
pollCount,
|
|
2466
|
-
note,
|
|
2467
|
-
}),
|
|
2468
|
-
});
|
|
2469
|
-
await abortableSleep(graceSeconds * 1000, signal);
|
|
2470
|
-
runs = await fetchRunsForCommit(this.session.cwd, repo, headSha, branch, signal);
|
|
2471
|
-
}
|
|
2472
|
-
|
|
2473
|
-
const failedJobLogs = await fetchFailedJobLogs(
|
|
2474
|
-
this.session.cwd,
|
|
2475
|
-
repo,
|
|
2476
|
-
runs.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job }))),
|
|
2477
|
-
tail,
|
|
2478
|
-
signal,
|
|
2479
|
-
);
|
|
2480
|
-
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2481
|
-
state: "completed",
|
|
2482
|
-
failedJobLogs,
|
|
2483
|
-
});
|
|
2484
|
-
const artifactId = await saveArtifactText(
|
|
2485
|
-
this.session,
|
|
2486
|
-
this.name,
|
|
2487
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail, { mode: "full" }),
|
|
2488
|
-
);
|
|
2489
|
-
return buildTextResult(
|
|
2490
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail),
|
|
2491
|
-
undefined,
|
|
2492
|
-
{ ...finalDetails, artifactId },
|
|
2493
|
-
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2494
|
-
);
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
if (outcome === "success") {
|
|
2498
|
-
const signature = getRunCollectionSignature(runs);
|
|
2499
|
-
if (signature === settledSuccessSignature) {
|
|
2500
|
-
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2501
|
-
state: "completed",
|
|
2502
|
-
});
|
|
2503
|
-
return buildTextResult(
|
|
2504
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, [], tail),
|
|
2505
|
-
undefined,
|
|
2506
|
-
finalDetails,
|
|
2507
|
-
);
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
settledSuccessSignature = signature;
|
|
2511
|
-
const note = `All known workflow runs completed successfully. Waiting ${intervalSeconds}s to ensure no additional runs appear for this commit.`;
|
|
2512
|
-
onUpdate?.({
|
|
2513
|
-
content: [
|
|
2514
|
-
{
|
|
2515
|
-
type: "text",
|
|
2516
|
-
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2517
|
-
},
|
|
2518
|
-
],
|
|
2519
|
-
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2520
|
-
state: "watching",
|
|
2521
|
-
pollCount,
|
|
2522
|
-
note,
|
|
2523
|
-
}),
|
|
2524
|
-
});
|
|
2525
|
-
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2526
|
-
continue;
|
|
2527
|
-
}
|
|
2326
|
+
note,
|
|
2327
|
+
}),
|
|
2328
|
+
});
|
|
2329
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2528
2332
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
}
|
|
2532
|
-
});
|
|
2333
|
+
settledSuccessSignature = undefined;
|
|
2334
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2533
2335
|
}
|
|
2534
2336
|
}
|