@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.1
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 +98 -1
- package/package.json +7 -7
- package/src/autoresearch/prompt.md +1 -1
- 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 +4 -4
- package/src/cursor.ts +3 -8
- package/src/discovery/helpers.ts +3 -3
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +86 -57
- package/src/edit/line-hash.ts +743 -24
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +893 -0
- package/src/edit/modes/chunk.ts +14 -24
- package/src/edit/modes/hashline.ts +193 -146
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +14 -10
- package/src/edit/streaming.ts +50 -16
- package/src/exec/bash-executor.ts +2 -4
- 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 +2 -2
- package/src/lsp/defaults.json +142 -652
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/components/assistant-message.ts +4 -0
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/session-selector.ts +3 -3
- 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 +1 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/print-mode.ts +8 -0
- 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 +3 -0
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +16 -20
- package/src/prompts/tools/ast-grep.md +19 -24
- package/src/prompts/tools/atom.md +87 -0
- package/src/prompts/tools/chunk-edit.md +37 -161
- 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 +4 -5
- 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 +1 -1
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +2 -3
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +14 -9
- package/src/session/agent-session.ts +25 -2
- package/src/session/session-manager.ts +4 -1
- package/src/task/executor.ts +43 -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 +8 -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 +509 -697
- package/src/tools/grep.ts +116 -131
- 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 +8 -7
- 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 -25
- 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/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/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 -12
- 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
|
-
|
|
193
|
-
}),
|
|
194
|
-
),
|
|
195
|
-
repo: Type.Optional(
|
|
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.",
|
|
160
|
+
description: "issue number or url (issue_view)",
|
|
161
|
+
examples: ["123", "https://github.com/owner/repo/issues/123"],
|
|
205
162
|
}),
|
|
206
163
|
),
|
|
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;
|
|
@@ -757,7 +693,7 @@ async function resolvePrBranchPushTarget(
|
|
|
757
693
|
}> {
|
|
758
694
|
const headRef = await git.config.getBranch(repoRoot, localBranch, "ompPrHeadRef", signal);
|
|
759
695
|
if (!headRef) {
|
|
760
|
-
throw new ToolError(`branch ${localBranch} has no PR push metadata; check it out via
|
|
696
|
+
throw new ToolError(`branch ${localBranch} has no PR push metadata; check it out via op: pr_checkout first`);
|
|
761
697
|
}
|
|
762
698
|
|
|
763
699
|
const pushRemote = await git.config.getBranch(repoRoot, localBranch, "pushRemote", signal);
|
|
@@ -1638,7 +1574,7 @@ function formatReviewCommentsSection(comments: GhPrReviewComment[] | undefined):
|
|
|
1638
1574
|
return lines;
|
|
1639
1575
|
}
|
|
1640
1576
|
|
|
1641
|
-
function formatRepoView(data: GhRepoViewData, input:
|
|
1577
|
+
function formatRepoView(data: GhRepoViewData, input: { repo?: string; branch?: string }): string {
|
|
1642
1578
|
const lines: string[] = [];
|
|
1643
1579
|
const name = data.nameWithOwner ?? input.repo ?? "GitHub Repository";
|
|
1644
1580
|
lines.push(`# ${name}`);
|
|
@@ -1665,7 +1601,7 @@ function formatRepoView(data: GhRepoViewData, input: GhRepoViewInput): string {
|
|
|
1665
1601
|
return lines.join("\n").trim();
|
|
1666
1602
|
}
|
|
1667
1603
|
|
|
1668
|
-
function formatIssueView(data: GhIssueViewData, input:
|
|
1604
|
+
function formatIssueView(data: GhIssueViewData, input: { issue: string; repo?: string; comments?: boolean }): string {
|
|
1669
1605
|
const lines: string[] = [];
|
|
1670
1606
|
const issueNumber = data.number ?? input.issue;
|
|
1671
1607
|
lines.push(`# Issue #${issueNumber}: ${data.title ?? "Untitled"}`);
|
|
@@ -1711,7 +1647,7 @@ function formatPrFiles(files: GhPrFile[] | undefined): string[] {
|
|
|
1711
1647
|
return lines;
|
|
1712
1648
|
}
|
|
1713
1649
|
|
|
1714
|
-
function formatPrView(data: GhPrViewData, input:
|
|
1650
|
+
function formatPrView(data: GhPrViewData, input: { pr?: string; repo?: string; comments?: boolean }): string {
|
|
1715
1651
|
const lines: string[] = [];
|
|
1716
1652
|
const prIdentifier = data.number ?? input.pr ?? "current";
|
|
1717
1653
|
lines.push(`# Pull Request #${prIdentifier}: ${data.title ?? "Untitled"}`);
|
|
@@ -1881,644 +1817,520 @@ function buildTextResult(
|
|
|
1881
1817
|
return builder.done();
|
|
1882
1818
|
}
|
|
1883
1819
|
|
|
1884
|
-
export class
|
|
1885
|
-
readonly name = "
|
|
1886
|
-
readonly label = "GitHub
|
|
1887
|
-
readonly description = prompt.render(
|
|
1888
|
-
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;
|
|
1889
1825
|
readonly strict = true;
|
|
1890
1826
|
|
|
1891
1827
|
constructor(private readonly session: ToolSession) {}
|
|
1892
1828
|
|
|
1893
|
-
static createIf(session: ToolSession):
|
|
1829
|
+
static createIf(session: ToolSession): GithubTool | null {
|
|
1894
1830
|
if (!git.github.available()) return null;
|
|
1895
|
-
return new
|
|
1831
|
+
return new GithubTool(session);
|
|
1896
1832
|
}
|
|
1897
1833
|
|
|
1898
1834
|
async execute(
|
|
1899
1835
|
_toolCallId: string,
|
|
1900
|
-
params:
|
|
1836
|
+
params: GithubInput,
|
|
1901
1837
|
signal?: AbortSignal,
|
|
1902
|
-
|
|
1838
|
+
onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
1903
1839
|
_context?: AgentToolContext,
|
|
1904
1840
|
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1905
1841
|
return untilAborted(signal, async () => {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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);
|
|
1914
1861
|
}
|
|
1915
|
-
args.push("--json", GH_REPO_FIELDS.join(","));
|
|
1916
|
-
|
|
1917
|
-
const data = await git.github.json<GhRepoViewData>(this.session.cwd, args, signal, {
|
|
1918
|
-
repoProvided: Boolean(repo),
|
|
1919
|
-
});
|
|
1920
|
-
return buildTextResult(formatRepoView(data, { repo, branch }), data.url);
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
export class GhIssueViewTool implements AgentTool<typeof ghIssueViewSchema, GhToolDetails> {
|
|
1926
|
-
readonly name = "gh_issue_view";
|
|
1927
|
-
readonly label = "GitHub Issue";
|
|
1928
|
-
readonly description = prompt.render(ghIssueViewDescription);
|
|
1929
|
-
readonly parameters = ghIssueViewSchema;
|
|
1930
|
-
readonly strict = true;
|
|
1931
|
-
|
|
1932
|
-
constructor(private readonly session: ToolSession) {}
|
|
1933
|
-
|
|
1934
|
-
static createIf(session: ToolSession): GhIssueViewTool | null {
|
|
1935
|
-
if (!git.github.available()) return null;
|
|
1936
|
-
return new GhIssueViewTool(session);
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
async execute(
|
|
1940
|
-
_toolCallId: string,
|
|
1941
|
-
params: GhIssueViewInput,
|
|
1942
|
-
signal?: AbortSignal,
|
|
1943
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
1944
|
-
_context?: AgentToolContext,
|
|
1945
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
1946
|
-
return untilAborted(signal, async () => {
|
|
1947
|
-
const issue = requireNonEmpty(params.issue, "issue");
|
|
1948
|
-
const repo = normalizeOptionalString(params.repo);
|
|
1949
|
-
const includeComments = params.comments ?? true;
|
|
1950
|
-
const args = ["issue", "view", issue];
|
|
1951
|
-
appendRepoFlag(args, repo, issue);
|
|
1952
|
-
args.push("--json", (includeComments ? GH_ISSUE_FIELDS : GH_ISSUE_FIELDS_NO_COMMENTS).join(","));
|
|
1953
|
-
|
|
1954
|
-
const data = await git.github.json<GhIssueViewData>(this.session.cwd, args, signal, {
|
|
1955
|
-
repoProvided: Boolean(repo),
|
|
1956
|
-
});
|
|
1957
|
-
return buildTextResult(formatIssueView(data, { issue, repo, comments: includeComments }), data.url);
|
|
1958
1862
|
});
|
|
1959
1863
|
}
|
|
1960
1864
|
}
|
|
1961
1865
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
+
);
|
|
1975
2005
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const args = ["pr", "view"];
|
|
1988
|
-
if (pr) {
|
|
1989
|
-
args.push(pr);
|
|
1990
|
-
}
|
|
1991
|
-
appendRepoFlag(args, repo, pr);
|
|
1992
|
-
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
|
+
}
|
|
1993
2017
|
|
|
1994
|
-
|
|
1995
|
-
repoProvided: Boolean(repo),
|
|
1996
|
-
});
|
|
1997
|
-
const resolvedRepo = repo ?? parsePullRequestUrl(data.url).repo;
|
|
1998
|
-
if (includeComments && resolvedRepo && typeof data.number === "number") {
|
|
1999
|
-
data.reviewComments = await fetchPrReviewComments(this.session.cwd, resolvedRepo, data.number, signal);
|
|
2018
|
+
await git.branch.force(repoRoot, localBranch, `refs/remotes/${remote.name}/${headRefName}`, signal);
|
|
2000
2019
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
export class GhPrDiffTool implements AgentTool<typeof ghPrDiffSchema, GhToolDetails> {
|
|
2007
|
-
readonly name = "gh_pr_diff";
|
|
2008
|
-
readonly label = "GitHub PR Diff";
|
|
2009
|
-
readonly description = prompt.render(ghPrDiffDescription);
|
|
2010
|
-
readonly parameters = ghPrDiffSchema;
|
|
2011
|
-
readonly strict = true;
|
|
2012
|
-
|
|
2013
|
-
constructor(private readonly session: ToolSession) {}
|
|
2014
|
-
|
|
2015
|
-
static createIf(session: ToolSession): GhPrDiffTool | null {
|
|
2016
|
-
if (!git.github.available()) return null;
|
|
2017
|
-
return new GhPrDiffTool(session);
|
|
2020
|
+
} else {
|
|
2021
|
+
await git.branch.create(repoRoot, localBranch, `refs/remotes/${remote.name}/${headRefName}`, signal);
|
|
2022
|
+
}
|
|
2018
2023
|
}
|
|
2019
2024
|
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
for (const pattern of params.exclude ?? []) {
|
|
2040
|
-
const normalizedPattern = requireNonEmpty(pattern, "exclude pattern");
|
|
2041
|
-
args.push("--exclude", normalizedPattern);
|
|
2042
|
-
}
|
|
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
|
+
);
|
|
2043
2044
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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
|
+
);
|
|
2053
2071
|
}
|
|
2054
2072
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
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
|
+
}
|
|
2088
2113
|
|
|
2089
|
-
|
|
2090
|
-
|
|
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,
|
|
2091
2175
|
});
|
|
2092
|
-
const prNumber = data.number;
|
|
2093
|
-
if (typeof prNumber !== "number") {
|
|
2094
|
-
throw new ToolError("GitHub CLI did not return a pull request number.");
|
|
2095
|
-
}
|
|
2096
2176
|
|
|
2097
|
-
const
|
|
2098
|
-
const
|
|
2099
|
-
const repoRoot = await requireGitRepoRoot(this.session.cwd, signal);
|
|
2100
|
-
const primaryRepoRoot = await requirePrimaryGitRepoRoot(repoRoot, signal);
|
|
2101
|
-
const localBranch = requestedBranch ?? `pr-${prNumber}`;
|
|
2102
|
-
const worktreePath = requestedWorktree
|
|
2103
|
-
? path.resolve(this.session.cwd, requestedWorktree)
|
|
2104
|
-
: path.join(primaryRepoRoot, ".worktrees", localBranch);
|
|
2105
|
-
const existingWorktrees = await git.worktree.list(repoRoot, signal);
|
|
2106
|
-
const existingWorktree = existingWorktrees.find(entry => entry.branch === toLocalBranchRef(localBranch));
|
|
2107
|
-
|
|
2108
|
-
const remote = await ensurePrRemote(repoRoot, data, signal);
|
|
2109
|
-
await git.fetch(
|
|
2110
|
-
repoRoot,
|
|
2111
|
-
remote.name,
|
|
2112
|
-
`refs/heads/${headRefName}`,
|
|
2113
|
-
`refs/remotes/${remote.name}/${headRefName}`,
|
|
2114
|
-
signal,
|
|
2115
|
-
);
|
|
2177
|
+
const failedJobs = run.jobs.filter(isFailedJob);
|
|
2178
|
+
const runCompleted = run.status === "completed";
|
|
2116
2179
|
|
|
2117
|
-
if (
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
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);
|
|
2133
2198
|
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
await git.config.setBranch(repoRoot, localBranch, "remote", remote.name, signal);
|
|
2137
|
-
await git.config.setBranch(repoRoot, localBranch, "merge", `refs/heads/${headRefName}`, signal);
|
|
2138
|
-
await git.config.setBranch(repoRoot, localBranch, "pushRemote", remote.name, signal);
|
|
2139
|
-
await git.config.setBranch(repoRoot, localBranch, "ompPrHeadRef", headRefName, signal);
|
|
2140
|
-
await git.config.setBranch(repoRoot, localBranch, "ompPrUrl", data.url ?? "", signal);
|
|
2141
|
-
await git.config.setBranch(
|
|
2142
|
-
repoRoot,
|
|
2143
|
-
localBranch,
|
|
2144
|
-
"ompPrIsCrossRepository",
|
|
2145
|
-
String(Boolean(data.isCrossRepository)),
|
|
2146
|
-
signal,
|
|
2147
|
-
);
|
|
2148
|
-
await git.config.setBranch(
|
|
2149
|
-
repoRoot,
|
|
2150
|
-
localBranch,
|
|
2151
|
-
"ompPrMaintainerCanModify",
|
|
2152
|
-
String(Boolean(data.maintainerCanModify)),
|
|
2153
|
-
signal,
|
|
2154
|
-
);
|
|
2155
2199
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
+
);
|
|
2161
2222
|
}
|
|
2162
|
-
const resolvedWorktreePath = await fs.realpath(finalWorktreePath);
|
|
2163
|
-
|
|
2164
|
-
return buildTextResult(
|
|
2165
|
-
formatPrCheckoutResult({
|
|
2166
|
-
data,
|
|
2167
|
-
localBranch,
|
|
2168
|
-
worktreePath: resolvedWorktreePath,
|
|
2169
|
-
remoteName: remote.name,
|
|
2170
|
-
remoteUrl: remote.url,
|
|
2171
|
-
reused: Boolean(existingWorktree),
|
|
2172
|
-
}),
|
|
2173
|
-
data.url,
|
|
2174
|
-
{
|
|
2175
|
-
repo: repo ?? data.headRepository?.nameWithOwner,
|
|
2176
|
-
branch: localBranch,
|
|
2177
|
-
worktreePath: resolvedWorktreePath,
|
|
2178
|
-
remote: remote.name,
|
|
2179
|
-
remoteBranch: headRefName,
|
|
2180
|
-
},
|
|
2181
|
-
);
|
|
2182
|
-
});
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
2223
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
readonly strict = true;
|
|
2192
|
-
|
|
2193
|
-
constructor(private readonly session: ToolSession) {}
|
|
2194
|
-
|
|
2195
|
-
static createIf(session: ToolSession): GhPrPushTool | null {
|
|
2196
|
-
if (!git.github.available()) return null;
|
|
2197
|
-
return new GhPrPushTool(session);
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
async execute(
|
|
2201
|
-
_toolCallId: string,
|
|
2202
|
-
params: GhPrPushInput,
|
|
2203
|
-
signal?: AbortSignal,
|
|
2204
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2205
|
-
_context?: AgentToolContext,
|
|
2206
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2207
|
-
return untilAborted(signal, async () => {
|
|
2208
|
-
const repoRoot = await requireGitRepoRoot(this.session.cwd, signal);
|
|
2209
|
-
const localBranch =
|
|
2210
|
-
normalizeOptionalString(params.branch) ?? (await requireCurrentGitBranch(repoRoot, signal));
|
|
2211
|
-
const refExists = await git.ref.exists(repoRoot, toLocalBranchRef(localBranch), signal);
|
|
2212
|
-
if (!refExists) {
|
|
2213
|
-
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);
|
|
2214
2229
|
}
|
|
2215
2230
|
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
const sourceRef = currentBranch === localBranch ? "HEAD" : toLocalBranchRef(localBranch);
|
|
2219
|
-
const refspec = `${sourceRef}:refs/heads/${target.remoteBranch}`;
|
|
2220
|
-
await git.push(repoRoot, {
|
|
2221
|
-
forceWithLease: params.forceWithLease,
|
|
2222
|
-
refspec,
|
|
2223
|
-
remote: target.remoteName,
|
|
2224
|
-
signal,
|
|
2225
|
-
});
|
|
2226
|
-
|
|
2227
|
-
return buildTextResult(
|
|
2228
|
-
formatPrPushResult({
|
|
2229
|
-
localBranch,
|
|
2230
|
-
remoteName: target.remoteName,
|
|
2231
|
-
remoteBranch: target.remoteBranch,
|
|
2232
|
-
remoteUrl: target.remoteUrl,
|
|
2233
|
-
prUrl: target.prUrl,
|
|
2234
|
-
forceWithLease: params.forceWithLease ?? false,
|
|
2235
|
-
}),
|
|
2236
|
-
target.prUrl,
|
|
2237
|
-
{
|
|
2238
|
-
branch: localBranch,
|
|
2239
|
-
remote: target.remoteName,
|
|
2240
|
-
remoteBranch: target.remoteBranch,
|
|
2241
|
-
},
|
|
2242
|
-
);
|
|
2243
|
-
});
|
|
2231
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2232
|
+
}
|
|
2244
2233
|
}
|
|
2245
|
-
}
|
|
2246
2234
|
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
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;
|
|
2255
2241
|
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
async execute(
|
|
2262
|
-
_toolCallId: string,
|
|
2263
|
-
params: GhSearchIssuesInput,
|
|
2264
|
-
signal?: AbortSignal,
|
|
2265
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2266
|
-
_context?: AgentToolContext,
|
|
2267
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2268
|
-
return untilAborted(signal, async () => {
|
|
2269
|
-
const query = requireNonEmpty(params.query, "query");
|
|
2270
|
-
const repo = normalizeOptionalString(params.repo);
|
|
2271
|
-
const limit = resolveSearchLimit(params.limit);
|
|
2272
|
-
const args = buildGhSearchArgs("issues", query, limit, repo);
|
|
2242
|
+
while (true) {
|
|
2243
|
+
throwIfAborted(signal);
|
|
2244
|
+
pollCount += 1;
|
|
2273
2245
|
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2246
|
+
let runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
|
|
2247
|
+
const details = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2248
|
+
state: "watching",
|
|
2249
|
+
pollCount,
|
|
2278
2250
|
});
|
|
2279
|
-
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
export class GhSearchPrsTool implements AgentTool<typeof ghSearchPrsSchema, GhToolDetails> {
|
|
2283
|
-
readonly name = "gh_search_prs";
|
|
2284
|
-
readonly label = "GitHub PR Search";
|
|
2285
|
-
readonly description = prompt.render(ghSearchPrsDescription);
|
|
2286
|
-
readonly parameters = ghSearchPrsSchema;
|
|
2287
|
-
readonly strict = true;
|
|
2288
|
-
|
|
2289
|
-
constructor(private readonly session: ToolSession) {}
|
|
2290
|
-
|
|
2291
|
-
static createIf(session: ToolSession): GhSearchPrsTool | null {
|
|
2292
|
-
if (!git.github.available()) return null;
|
|
2293
|
-
return new GhSearchPrsTool(session);
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
async execute(
|
|
2297
|
-
_toolCallId: string,
|
|
2298
|
-
params: GhSearchPrsInput,
|
|
2299
|
-
signal?: AbortSignal,
|
|
2300
|
-
_onUpdate?: AgentToolUpdateCallback<GhToolDetails>,
|
|
2301
|
-
_context?: AgentToolContext,
|
|
2302
|
-
): Promise<AgentToolResult<GhToolDetails>> {
|
|
2303
|
-
return untilAborted(signal, async () => {
|
|
2304
|
-
const query = requireNonEmpty(params.query, "query");
|
|
2305
|
-
const repo = normalizeOptionalString(params.repo);
|
|
2306
|
-
const limit = resolveSearchLimit(params.limit);
|
|
2307
|
-
const args = buildGhSearchArgs("prs", query, limit, repo);
|
|
2308
|
-
|
|
2309
|
-
const items = await git.github.json<GhSearchResult[]>(this.session.cwd, args, signal, {
|
|
2310
|
-
repoProvided: Boolean(repo),
|
|
2311
|
-
});
|
|
2312
|
-
return buildTextResult(formatSearchResults("pull requests", query, repo, items));
|
|
2251
|
+
onUpdate?.({
|
|
2252
|
+
content: [{ type: "text", text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount) }],
|
|
2253
|
+
details,
|
|
2313
2254
|
});
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
export class GhRunWatchTool implements AgentTool<typeof ghRunWatchSchema, GhToolDetails> {
|
|
2318
|
-
readonly name = "gh_run_watch";
|
|
2319
|
-
readonly label = "GitHub Run Watch";
|
|
2320
|
-
readonly description = prompt.render(ghRunWatchDescription);
|
|
2321
|
-
readonly parameters = ghRunWatchSchema;
|
|
2322
|
-
readonly strict = true;
|
|
2323
|
-
|
|
2324
|
-
constructor(private readonly session: ToolSession) {}
|
|
2325
|
-
|
|
2326
|
-
static createIf(session: ToolSession): GhRunWatchTool | null {
|
|
2327
|
-
if (!git.github.available()) return null;
|
|
2328
|
-
return new GhRunWatchTool(session);
|
|
2329
|
-
}
|
|
2330
2255
|
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
const graceSeconds = RUN_WATCH_GRACE_DEFAULT;
|
|
2344
|
-
const tail = resolveTailLimit(params.tail);
|
|
2345
|
-
if (runReference.runId !== undefined) {
|
|
2346
|
-
const runId = runReference.runId;
|
|
2347
|
-
let pollCount = 0;
|
|
2348
|
-
|
|
2349
|
-
while (true) {
|
|
2350
|
-
throwIfAborted(signal);
|
|
2351
|
-
pollCount += 1;
|
|
2352
|
-
|
|
2353
|
-
let run = await fetchRunSnapshot(this.session.cwd, repo, runId, signal);
|
|
2354
|
-
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, {
|
|
2355
2268
|
state: "watching",
|
|
2356
2269
|
pollCount,
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
const failedJobs = run.jobs.filter(isFailedJob);
|
|
2364
|
-
const runCompleted = run.status === "completed";
|
|
2365
|
-
|
|
2366
|
-
if (failedJobs.length > 0) {
|
|
2367
|
-
if (!runCompleted && graceSeconds > 0) {
|
|
2368
|
-
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2369
|
-
onUpdate?.({
|
|
2370
|
-
content: [
|
|
2371
|
-
{
|
|
2372
|
-
type: "text",
|
|
2373
|
-
text: formatRunWatchSnapshot(repo, run, pollCount, note),
|
|
2374
|
-
},
|
|
2375
|
-
],
|
|
2376
|
-
details: buildRunWatchDetails(repo, run, {
|
|
2377
|
-
state: "watching",
|
|
2378
|
-
pollCount,
|
|
2379
|
-
note,
|
|
2380
|
-
}),
|
|
2381
|
-
});
|
|
2382
|
-
await abortableSleep(graceSeconds * 1000, signal);
|
|
2383
|
-
run = await fetchRunSnapshot(this.session.cwd, repo, runId, signal);
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
const failedJobLogs = await fetchFailedJobLogs(
|
|
2387
|
-
this.session.cwd,
|
|
2388
|
-
repo,
|
|
2389
|
-
run.jobs.filter(isFailedJob).map(job => ({ run, job })),
|
|
2390
|
-
tail,
|
|
2391
|
-
signal,
|
|
2392
|
-
);
|
|
2393
|
-
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2394
|
-
state: "completed",
|
|
2395
|
-
failedJobLogs,
|
|
2396
|
-
});
|
|
2397
|
-
const artifactId = await saveArtifactText(
|
|
2398
|
-
this.session,
|
|
2399
|
-
this.name,
|
|
2400
|
-
formatRunWatchResult(repo, run, failedJobLogs, tail, { mode: "full" }),
|
|
2401
|
-
);
|
|
2402
|
-
return buildTextResult(
|
|
2403
|
-
formatRunWatchResult(repo, run, failedJobLogs, tail),
|
|
2404
|
-
run.url,
|
|
2405
|
-
{ ...finalDetails, artifactId },
|
|
2406
|
-
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2407
|
-
);
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
if (runCompleted) {
|
|
2411
|
-
const finalDetails = buildRunWatchDetails(repo, run, {
|
|
2412
|
-
state: "completed",
|
|
2413
|
-
});
|
|
2414
|
-
return buildTextResult(formatRunWatchResult(repo, run, [], tail), run.url, finalDetails);
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2418
|
-
}
|
|
2270
|
+
note,
|
|
2271
|
+
}),
|
|
2272
|
+
});
|
|
2273
|
+
await abortableSleep(graceSeconds * 1000, signal);
|
|
2274
|
+
runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
|
|
2419
2275
|
}
|
|
2420
2276
|
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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
|
+
}
|
|
2427
2300
|
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
+
}
|
|
2431
2313
|
|
|
2432
|
-
|
|
2433
|
-
|
|
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, {
|
|
2434
2324
|
state: "watching",
|
|
2435
2325
|
pollCount,
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
const outcome = getRunCollectionOutcome(runs);
|
|
2443
|
-
if (outcome === "failure") {
|
|
2444
|
-
if (graceSeconds > 0) {
|
|
2445
|
-
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
2446
|
-
onUpdate?.({
|
|
2447
|
-
content: [
|
|
2448
|
-
{
|
|
2449
|
-
type: "text",
|
|
2450
|
-
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2451
|
-
},
|
|
2452
|
-
],
|
|
2453
|
-
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2454
|
-
state: "watching",
|
|
2455
|
-
pollCount,
|
|
2456
|
-
note,
|
|
2457
|
-
}),
|
|
2458
|
-
});
|
|
2459
|
-
await abortableSleep(graceSeconds * 1000, signal);
|
|
2460
|
-
runs = await fetchRunsForCommit(this.session.cwd, repo, headSha, branch, signal);
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
const failedJobLogs = await fetchFailedJobLogs(
|
|
2464
|
-
this.session.cwd,
|
|
2465
|
-
repo,
|
|
2466
|
-
runs.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job }))),
|
|
2467
|
-
tail,
|
|
2468
|
-
signal,
|
|
2469
|
-
);
|
|
2470
|
-
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2471
|
-
state: "completed",
|
|
2472
|
-
failedJobLogs,
|
|
2473
|
-
});
|
|
2474
|
-
const artifactId = await saveArtifactText(
|
|
2475
|
-
this.session,
|
|
2476
|
-
this.name,
|
|
2477
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail, { mode: "full" }),
|
|
2478
|
-
);
|
|
2479
|
-
return buildTextResult(
|
|
2480
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, failedJobLogs, tail),
|
|
2481
|
-
undefined,
|
|
2482
|
-
{ ...finalDetails, artifactId },
|
|
2483
|
-
{ artifactId, artifactLabel: "Full failed-job logs" },
|
|
2484
|
-
);
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
|
-
if (outcome === "success") {
|
|
2488
|
-
const signature = getRunCollectionSignature(runs);
|
|
2489
|
-
if (signature === settledSuccessSignature) {
|
|
2490
|
-
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2491
|
-
state: "completed",
|
|
2492
|
-
});
|
|
2493
|
-
return buildTextResult(
|
|
2494
|
-
formatCommitRunWatchResult(repo, headSha, branch, runs, [], tail),
|
|
2495
|
-
undefined,
|
|
2496
|
-
finalDetails,
|
|
2497
|
-
);
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
settledSuccessSignature = signature;
|
|
2501
|
-
const note = `All known workflow runs completed successfully. Waiting ${intervalSeconds}s to ensure no additional runs appear for this commit.`;
|
|
2502
|
-
onUpdate?.({
|
|
2503
|
-
content: [
|
|
2504
|
-
{
|
|
2505
|
-
type: "text",
|
|
2506
|
-
text: formatCommitRunWatchSnapshot(repo, headSha, branch, runs, pollCount, note),
|
|
2507
|
-
},
|
|
2508
|
-
],
|
|
2509
|
-
details: buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
2510
|
-
state: "watching",
|
|
2511
|
-
pollCount,
|
|
2512
|
-
note,
|
|
2513
|
-
}),
|
|
2514
|
-
});
|
|
2515
|
-
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2516
|
-
continue;
|
|
2517
|
-
}
|
|
2326
|
+
note,
|
|
2327
|
+
}),
|
|
2328
|
+
});
|
|
2329
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2518
2332
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
}
|
|
2522
|
-
});
|
|
2333
|
+
settledSuccessSignature = undefined;
|
|
2334
|
+
await abortableSleep(intervalSeconds * 1000, signal);
|
|
2523
2335
|
}
|
|
2524
2336
|
}
|