From 03387a0badc15f211222f1dc5c4cacc2043ed0e6 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Fri, 28 Aug 2026 19:19:10 +0100 Subject: [PATCH] feat(sdk): add getWorkflowJobLogs, PR review thread tools, and deleteBranch --- .../ci-logs-review-threads-delete-branch.md | 6 + AGENTS.md | 2 +- apps/chat/shared/utils/tools/github.ts | 5 + .../docs/1.getting-started/1.introduction.md | 2 +- .../docs/2.frameworks/1.eve-extension.md | 6 +- .../content/docs/2.frameworks/2.ai-sdk.md | 4 +- .../6.manager-agent-with-subagents.md | 2 +- .../docs/content/docs/3.examples/7.recipes.md | 2 +- apps/docs/content/docs/4.guide/1.presets.md | 6 +- .../docs/4.guide/2.approval-control.md | 3 + .../content/docs/4.guide/6.working-context.md | 2 + .../content/docs/5.api/1.tools-catalog.md | 5 + apps/docs/content/docs/6.deprecated/1.eve.md | 4 +- apps/docs/skills/github-tools-agents/SKILL.md | 10 +- packages/github-tools/README.md | 29 +-- packages/github-tools/src/agents.ts | 4 + packages/github-tools/src/connect/scopes.ts | 5 + packages/github-tools/src/core/presets.ts | 35 ++-- .../github-tools/src/core/pull-requests.ts | 188 ++++++++++++++++++ packages/github-tools/src/core/repository.ts | 16 ++ packages/github-tools/src/core/tool-names.ts | 10 + .../github-tools/src/core/workflows.test.ts | 35 ++++ packages/github-tools/src/core/workflows.ts | 33 +++ packages/github-tools/src/core/write-tools.ts | 6 + packages/github-tools/src/eve/registry.ts | 33 +++ packages/github-tools/src/index.ts | 17 +- .../github-tools/src/tools/pull-requests.ts | 50 +++++ packages/github-tools/src/tools/repository.ts | 17 ++ packages/github-tools/src/tools/workflows.ts | 16 ++ 29 files changed, 501 insertions(+), 52 deletions(-) create mode 100644 .changeset/ci-logs-review-threads-delete-branch.md create mode 100644 packages/github-tools/src/core/workflows.test.ts diff --git a/.changeset/ci-logs-review-threads-delete-branch.md b/.changeset/ci-logs-review-threads-delete-branch.md new file mode 100644 index 0000000..90f7170 --- /dev/null +++ b/.changeset/ci-logs-review-threads-delete-branch.md @@ -0,0 +1,6 @@ +--- +"@github-tools/sdk": minor +"@github-tools/eve-extension": minor +--- + +Add five tools (84 total): `getWorkflowJobLogs` reads a workflow job's log output, returning the last `maxLines` lines (default 200, max 2000) with per-line timestamps stripped to keep token usage low. `listPullRequestReviewThreads` lists PR review threads via GraphQL with resolution state and the IDs needed to reply or resolve — unresolved threads only and truncated comment bodies by default (`status: 'all'`, `detail: 'full'` to override). `replyToReviewComment` and `resolveReviewThread` answer and close review threads, and `deleteBranch` deletes a branch — all three are write tools requiring approval by default. Presets updated: `ci-ops`, `security-audit`, and `repo-explorer` gain job logs; `code-review` and `pr-author` gain the review-thread tools; `pr-author` also gains `deleteBranch`. diff --git a/AGENTS.md b/AGENTS.md index 9d97279..aa12bda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ ## Overview -`@github-tools/sdk` wraps GitHub's REST API as 79 AI SDK-compatible tools for agents and `generateText`/`streamText` calls — with presets, approval control, and integrations for eve, Vercel Workflow, and Chat SDK. Docs: [github-tools.com](https://github-tools.com). +`@github-tools/sdk` wraps GitHub's REST API as 84 AI SDK-compatible tools for agents and `generateText`/`streamText` calls — with presets, approval control, and integrations for eve, Vercel Workflow, and Chat SDK. Docs: [github-tools.com](https://github-tools.com). ## Commands diff --git a/apps/chat/shared/utils/tools/github.ts b/apps/chat/shared/utils/tools/github.ts index adb7872..4d9861b 100644 --- a/apps/chat/shared/utils/tools/github.ts +++ b/apps/chat/shared/utils/tools/github.ts @@ -16,6 +16,7 @@ export const GITHUB_TOOL_META: Record = { getFileContent: { title: 'Get File Content', label: 'File read', labelActive: 'Reading file', icon: 'i-lucide-file-code' }, getRepositoryTree: { title: 'Get Repository Tree', label: 'Tree fetched', labelActive: 'Fetching repository tree', icon: 'i-lucide-folder-tree' }, createBranch: { title: 'Create Branch', label: 'Branch created', labelActive: 'Creating branch', icon: 'i-lucide-git-branch-plus' }, + deleteBranch: { title: 'Delete Branch', label: 'Branch deleted', labelActive: 'Deleting branch', icon: 'i-lucide-git-branch-minus' }, forkRepository: { title: 'Fork Repository', label: 'Repository forked', labelActive: 'Forking repository', icon: 'i-lucide-git-fork' }, createRepository: { title: 'Create Repository', label: 'Repository created', labelActive: 'Creating repository', icon: 'i-lucide-plus' }, createOrUpdateFile: { title: 'Create / Update File', label: 'File updated', labelActive: 'Updating file', icon: 'i-lucide-file-pen' }, @@ -30,6 +31,9 @@ export const GITHUB_TOOL_META: Record = { listPullRequestFiles: { title: 'List PR Files', label: 'Files listed', labelActive: 'Listing PR files', icon: 'i-lucide-file-diff' }, listPullRequestReviews: { title: 'List PR Reviews', label: 'Reviews listed', labelActive: 'Listing PR reviews', icon: 'i-lucide-message-circle' }, createPullRequestReview: { title: 'Submit PR Review', label: 'Review submitted', labelActive: 'Submitting PR review', icon: 'i-lucide-shield-check' }, + listPullRequestReviewThreads: { title: 'List Review Threads', label: 'Review threads listed', labelActive: 'Listing review threads', icon: 'i-lucide-messages-square' }, + replyToReviewComment: { title: 'Reply to Review Comment', label: 'Reply posted', labelActive: 'Replying to review comment', icon: 'i-lucide-reply' }, + resolveReviewThread: { title: 'Resolve Review Thread', label: 'Thread resolved', labelActive: 'Resolving review thread', icon: 'i-lucide-check-check' }, requestReviewers: { title: 'Request Reviewers', label: 'Reviewers requested', labelActive: 'Requesting reviewers', icon: 'i-lucide-user-plus' }, getPullRequestContext: { title: 'PR Context', label: 'PR context loaded', labelActive: 'Loading PR context', icon: 'i-lucide-layers' }, listIssues: { title: 'List Issues', label: 'Issues listed', labelActive: 'Listing issues', icon: 'i-lucide-circle-dot' }, @@ -77,6 +81,7 @@ export const GITHUB_TOOL_META: Record = { listWorkflowRuns: { title: 'List Workflow Runs', label: 'Runs listed', labelActive: 'Listing workflow runs', icon: 'i-lucide-play' }, getWorkflowRun: { title: 'Get Workflow Run', label: 'Run fetched', labelActive: 'Fetching workflow run', icon: 'i-lucide-play' }, listWorkflowJobs: { title: 'List Workflow Jobs', label: 'Jobs listed', labelActive: 'Listing workflow jobs', icon: 'i-lucide-list-checks' }, + getWorkflowJobLogs: { title: 'Get Job Logs', label: 'Job logs fetched', labelActive: 'Fetching job logs', icon: 'i-lucide-scroll-text' }, triggerWorkflow: { title: 'Trigger Workflow', label: 'Workflow triggered', labelActive: 'Triggering workflow', icon: 'i-lucide-rocket' }, cancelWorkflowRun: { title: 'Cancel Workflow Run', label: 'Run cancelled', labelActive: 'Cancelling workflow run', icon: 'i-lucide-circle-x' }, rerunWorkflowRun: { title: 'Re-run Workflow', label: 'Workflow re-run', labelActive: 'Re-running workflow', icon: 'i-lucide-refresh-cw' }, diff --git a/apps/docs/content/docs/1.getting-started/1.introduction.md b/apps/docs/content/docs/1.getting-started/1.introduction.md index fdb6475..7e38b0b 100644 --- a/apps/docs/content/docs/1.getting-started/1.introduction.md +++ b/apps/docs/content/docs/1.getting-started/1.introduction.md @@ -117,7 +117,7 @@ The direct [`@github-tools/sdk/eve`](/deprecated/eve) import is deprecated in fa ## Explore the tools -The SDK covers repositories, branches, pull requests, issues, reactions, discussions, notifications, commits, releases, checks and statuses, code search, gists, and workflows: 79 tools in total. Each tool wraps a GitHub API operation (mostly REST; line-level blame and the discussion tools use GraphQL) and is fully typed with [Zod](https://zod.dev) schemas. +The SDK covers repositories, branches, pull requests, issues, reactions, discussions, notifications, commits, releases, checks and statuses, code search, gists, and workflows: 84 tools in total. Each tool wraps a GitHub API operation (mostly REST; line-level blame and the discussion tools use GraphQL) and is fully typed with [Zod](https://zod.dev) schemas. Browse the full list in the [Tools Catalog](/api/tools-catalog). diff --git a/apps/docs/content/docs/2.frameworks/1.eve-extension.md b/apps/docs/content/docs/2.frameworks/1.eve-extension.md index ab83364..0698168 100644 --- a/apps/docs/content/docs/2.frameworks/1.eve-extension.md +++ b/apps/docs/content/docs/2.frameworks/1.eve-extension.md @@ -1,6 +1,6 @@ --- title: Build a GitHub agent with the eve extension -description: Mount @github-tools/eve-extension under agent/extensions/ to add all 79 GitHub tools to an eve agent, the recommended way to wire GitHub into eve, with durable approval and Vercel Connect support. +description: Mount @github-tools/eve-extension under agent/extensions/ to add all 84 GitHub tools to an eve agent, the recommended way to wire GitHub into eve, with durable approval and Vercel Connect support. seo: title: Build a GitHub agent with the eve extension description: Mount @github-tools/eve-extension under agent/extensions/, the recommended way to add GitHub tools to an eve agent. @@ -28,7 +28,7 @@ links: variant: subtle --- -[eve](https://eve.dev) is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. `@github-tools/eve-extension` packages all 79 GitHub tools as a mountable [eve extension](https://eve.dev/docs/extensions): a single `pnpm add` and a one-line mount under `agent/extensions/`, no CLI setup, and no direct SDK import in `agent/tools/`. +[eve](https://eve.dev) is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. `@github-tools/eve-extension` packages all 84 GitHub tools as a mountable [eve extension](https://eve.dev/docs/extensions): a single `pnpm add` and a one-line mount under `agent/extensions/`, no CLI setup, and no direct SDK import in `agent/tools/`. ::callout{icon="i-custom:eve"} This is the **recommended way** to add GitHub tools to an eve agent. The legacy direct registration APIs (`createGithubTools` and per-tool factories from [`@github-tools/sdk/eve`](/deprecated/eve)) are **deprecated** in its favor. They keep working for existing `agent/tools/` setups, but new agents should mount the extension instead. Shared runtime helpers used by this extension live on `@github-tools/sdk/eve-runtime` (not deprecated). @@ -175,7 +175,7 @@ export default githubExtension({ ```ts [agent/extensions/github.ts] export default githubExtension({ - preset: 'maintainer', // all 79 tools + preset: 'maintainer', // all 84 tools exclude: ['createRepository', 'deleteGist'], // minus these two }) ``` diff --git a/apps/docs/content/docs/2.frameworks/2.ai-sdk.md b/apps/docs/content/docs/2.frameworks/2.ai-sdk.md index e60af71..1a01055 100644 --- a/apps/docs/content/docs/2.frameworks/2.ai-sdk.md +++ b/apps/docs/content/docs/2.frameworks/2.ai-sdk.md @@ -118,7 +118,7 @@ Full policy options (including `overrides.needsApproval`): [Control write safety ## Trim tool context with toolpick -With all 79 tools visible on every step, tool definitions eat tokens. [toolpick](https://github.com/pontusab/toolpick) selects only the most relevant tools per step: +With all 84 tools visible on every step, tool definitions eat tokens. [toolpick](https://github.com/pontusab/toolpick) selects only the most relevant tools per step: ```ts [with-toolpick.ts] import { createGithubTools } from '@github-tools/sdk' @@ -153,7 +153,7 @@ const tools = { } ``` -See the [Tools Catalog](/api/tools-catalog) for all 79 factories. +See the [Tools Catalog](/api/tools-catalog) for all 84 factories. ## When to level up diff --git a/apps/docs/content/docs/3.examples/6.manager-agent-with-subagents.md b/apps/docs/content/docs/3.examples/6.manager-agent-with-subagents.md index c3b77f6..6ec0d25 100644 --- a/apps/docs/content/docs/3.examples/6.manager-agent-with-subagents.md +++ b/apps/docs/content/docs/3.examples/6.manager-agent-with-subagents.md @@ -46,7 +46,7 @@ Build an eve manager agent that delegates GitHub work to specialist sub-agents i :: -Instead of one agent holding all 79 tools, this manager holds none. It reads the request, picks the right specialist, and calls it. Each specialist is a normal `@github-tools/eve-extension` mount scoped to a single [preset](/guide/presets), isolated in its own [declared subagent](https://eve.dev/docs/subagents) directory with its own tools, instructions, and approval policy. +Instead of one agent holding all 84 tools, this manager holds none. It reads the request, picks the right specialist, and calls it. Each specialist is a normal `@github-tools/eve-extension` mount scoped to a single [preset](/guide/presets), isolated in its own [declared subagent](https://eve.dev/docs/subagents) directory with its own tools, instructions, and approval policy. ::callout{icon="i-lucide-layers"} **Recommended for multi-role products.** Prefer this composition over putting `maintainer` (or omitting `preset`) on a single agent when the product has several distinct jobs — review, triage, release, and so on. Each specialist stays small in context and scoped in token permissions. diff --git a/apps/docs/content/docs/3.examples/7.recipes.md b/apps/docs/content/docs/3.examples/7.recipes.md index 58332e9..9bfe7a8 100644 --- a/apps/docs/content/docs/3.examples/7.recipes.md +++ b/apps/docs/content/docs/3.examples/7.recipes.md @@ -124,7 +124,7 @@ See the [full eve version of this task](/examples/eve-stale-issue-triager) for a ## Reduce tool context with toolpick -With all 79 tools visible on every step, tool definitions eat tokens. [toolpick](https://github.com/pontusab/toolpick) selects only the most relevant ones per step: +With all 84 tools visible on every step, tool definitions eat tokens. [toolpick](https://github.com/pontusab/toolpick) selects only the most relevant ones per step: ```ts [with-toolpick.ts] import { createGithubTools } from '@github-tools/sdk' diff --git a/apps/docs/content/docs/4.guide/1.presets.md b/apps/docs/content/docs/4.guide/1.presets.md index c757141..0830a38 100644 --- a/apps/docs/content/docs/4.guide/1.presets.md +++ b/apps/docs/content/docs/4.guide/1.presets.md @@ -96,14 +96,14 @@ const tools = createGithubTools({ | Preset | Tools included | Use case | |---|---|---| | `repo-explorer` | repository metadata, branches, file content, repo tree, code and issue search, discussions, gists, workflows, checks/statuses, releases | knowledge retrieval, repo Q&A | -| `ci-ops` | workflows, runs, jobs, checks/statuses, commits, repository context | CI monitoring, build ops | -| `code-review` | pull requests, commits, compare diff, file diffs, checks/statuses, updates, review comments, reviewer requests | PR copilots, change summaries | +| `ci-ops` | workflows, runs, jobs, job logs, checks/statuses, commits, repository context | CI monitoring, build ops | +| `code-review` | pull requests, commits, compare diff, file diffs, checks/statuses, updates, review comments, review threads, reviewer requests | PR copilots, change summaries | | `issue-triage` | issues, issue search, labels, comments, reactions, assignees, close/create/update/reopen | support triage, backlog bots | | `security-audit` | read-only exploration, code and issue search, PR/CI visibility, checks/statuses, compare diff, plus issue creation to report findings | vulnerability scanning, risk reporting | | `release-manager` | releases, compare diff, commits, workflow runs, pull requests, update/delete releases | changelog generation, release cutting | | `discussion-moderator` | discussions list/get/comment, plus light issue context | forum / Q&A bots | | `notification-inbox` | notifications list/mark-read, plus get issue/PR/repo | inbox triage (needs a Notifications PAT) | -| `pr-author` | branches, file edits, create/update pull requests, compare | open focused PRs without full maintainer | +| `pr-author` | branches (create/delete), file edits, create/update pull requests, review threads, compare | open focused PRs and address review feedback without full maintainer | | `maintainer` | all tool families including branch creation, forking, repo creation, discussions, notifications, gists, and workflows | full operator workflows with [approval control](/guide/approval-control) | ## Pair presets with token scopes diff --git a/apps/docs/content/docs/4.guide/2.approval-control.md b/apps/docs/content/docs/4.guide/2.approval-control.md index 1d45620..357eb12 100644 --- a/apps/docs/content/docs/4.guide/2.approval-control.md +++ b/apps/docs/content/docs/4.guide/2.approval-control.md @@ -89,10 +89,13 @@ const tools = createGithubTools({ | `createPullRequest` | Medium | Optional in trusted CI | | `updatePullRequest` | Medium | Require in production repos | | `createBranch` | Low | Usually skip | +| `deleteBranch` | High | Always require approval | | `addPullRequestComment` | Low | Usually skip | | `updatePullRequestComment` | Low | Usually skip | | `deletePullRequestComment` | Medium | Require in production repos | | `createPullRequestReview` | Medium | Require in production repos | +| `replyToReviewComment` | Low | Usually skip | +| `resolveReviewThread` | Low | Usually skip | | `requestReviewers` | Low | Usually skip | | `addIssueComment` | Low | Usually skip | | `updateIssueComment` | Low | Usually skip | diff --git a/apps/docs/content/docs/4.guide/6.working-context.md b/apps/docs/content/docs/4.guide/6.working-context.md index ddcdceb..cd32c65 100644 --- a/apps/docs/content/docs/4.guide/6.working-context.md +++ b/apps/docs/content/docs/4.guide/6.working-context.md @@ -69,6 +69,8 @@ Call independent follow-up reads **in the same step** when you already know the | `maxPages` | List tools fetch one page by default | Set `maxPages` to combine sequential pages in one call | | Text-match fragments | `searchCode` truncates each snippet to ~300 chars | None — fetch the file with `getFileContent` for full context | | `listDiscussions` | Returns 20 discussions per call, cursor-paginated | Raise `perPage`, or pass the returned `endCursor` as `after` | +| `getWorkflowJobLogs` | Returns the last 200 log lines with per-line timestamps stripped | Raise `maxLines` (up to 2000) when the error is higher up | +| `listPullRequestReviewThreads` | Returns unresolved threads only, comment bodies truncated (~500 chars), cursor-paginated | `status: 'all'` for resolved threads; `detail: 'full'` for complete bodies; pass `endCursor` as `after` | | `listNotifications` | Returns 20 unread threads per call (max 50) | `all: true` to include read threads; raise `perPage` | ## Example: code review bootstrap diff --git a/apps/docs/content/docs/5.api/1.tools-catalog.md b/apps/docs/content/docs/5.api/1.tools-catalog.md index c7e953c..f3bdd5c 100644 --- a/apps/docs/content/docs/5.api/1.tools-catalog.md +++ b/apps/docs/content/docs/5.api/1.tools-catalog.md @@ -54,6 +54,7 @@ Available in all presets. These tools manage repositories, branches, and file co | `getFileContent` | read a file at a path/ref; prefer `startLine`/`endLine` or `maxLines` for large files | No | | `getRepositoryTree` | list the file and directory structure of a repository at a given ref | No | | `createBranch` | create a new branch from an existing branch or commit SHA | Yes | +| `deleteBranch` | permanently delete a branch | Yes | | `forkRepository` | fork a repository to your account or an organization | Yes | | `createRepository` | create a new repository for the authenticated user or an organization | Yes | | `createOrUpdateFile` | create or update a file in the repository | Yes | @@ -68,6 +69,7 @@ Available in `code-review` and `maintainer` presets: | `getPullRequest` | read PR details including diff stats (body truncated by default; set `detail: full` for complete text) | No | | `listPullRequestFiles` | list files changed in a PR (patches omitted by default; set `includePatch` / `filenames` for diffs) | No | | `listPullRequestReviews` | list reviews on a PR (approvals, change requests, comments) | No | +| `listPullRequestReviewThreads` | list review threads with comments, resolution state, and reply/resolve IDs (unresolved only by default) | No | | `getPullRequestContext` | fetch PR details plus files, reviews, and optional CI checks in one call | No | | `createPullRequest` | open a new pull request | Yes | | `mergePullRequest` | merge a pull request | Yes | @@ -76,6 +78,8 @@ Available in `code-review` and `maintainer` presets: | `updatePullRequestComment` | edit the body of a pull request comment | Yes | | `deletePullRequestComment` | permanently delete a pull request comment | Yes | | `createPullRequestReview` | submit a formal review (approve, request changes, or comment) with inline comments | Yes | +| `replyToReviewComment` | reply to a review comment in its thread | Yes | +| `resolveReviewThread` | mark a review thread as resolved | Yes | | `requestReviewers` | request reviews from users or teams on a pull request | Yes | ## Issue tools @@ -163,6 +167,7 @@ Available in `repo-explorer` (read-only), `ci-ops`, and `maintainer` presets: | `listWorkflowRuns` | list workflow runs filtered by workflow, branch, status, or event | No | | `getWorkflowRun` | read a workflow run's status, timing, and trigger info | No | | `listWorkflowJobs` | list jobs in a workflow run with step-level status | No | +| `getWorkflowJobLogs` | read a job's log output (last 200 lines by default, timestamps stripped; raise `maxLines` for more) | No | | `triggerWorkflow` | trigger a workflow via workflow_dispatch event | Yes | | `cancelWorkflowRun` | cancel an in-progress workflow run | Yes | | `rerunWorkflowRun` | re-run a workflow run, optionally only failed jobs | Yes | diff --git a/apps/docs/content/docs/6.deprecated/1.eve.md b/apps/docs/content/docs/6.deprecated/1.eve.md index 71cd625..2b717b7 100644 --- a/apps/docs/content/docs/6.deprecated/1.eve.md +++ b/apps/docs/content/docs/6.deprecated/1.eve.md @@ -1,6 +1,6 @@ --- title: Build a GitHub agent with eve (direct import) -description: Deprecated. The direct @github-tools/sdk/eve import registers all 79 tools via defineDynamic with durable human-in-the-loop approval. Prefer the eve extension for new agents. +description: Deprecated. The direct @github-tools/sdk/eve import registers all 84 tools via defineDynamic with durable human-in-the-loop approval. Prefer the eve extension for new agents. seo: title: Build a GitHub agent with eve (direct import, deprecated) description: Deprecated direct-import path for eve agents. See the eve extension for the recommended approach. @@ -36,7 +36,7 @@ links: **Deprecated.** The **direct registration APIs** on this page — `createGithubTools`, the per-tool factories, and `connectGithubTools` from `@github-tools/sdk/connect/eve` — are deprecated in favor of [`@github-tools/eve-extension`](/frameworks/eve-extension). They keep working for existing `agent/tools/` agents and aren't being removed, but new agents should [mount the extension](/frameworks/eve-extension) instead. Shared runtime helpers used by the extension (`listEveToolDescriptors`, `executeGithubEveTool`, approval mappers, …) live on **`@github-tools/sdk/eve-runtime`** and are **not** deprecated. :: -[eve](https://eve.dev) is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. With `@github-tools/sdk/eve`, that folder becomes a **complete GitHub agent in 3 files**: all 79 tools registered from a single file, with durable human-in-the-loop approval that actually pauses the session until a person approves. This page documents the legacy direct-import path; for new agents, see the [eve extension](/frameworks/eve-extension) guide instead. +[eve](https://eve.dev) is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. With `@github-tools/sdk/eve`, that folder becomes a **complete GitHub agent in 3 files**: all 84 tools registered from a single file, with durable human-in-the-loop approval that actually pauses the session until a person approves. This page documents the legacy direct-import path; for new agents, see the [eve extension](/frameworks/eve-extension) guide instead. ::prompt --- diff --git a/apps/docs/skills/github-tools-agents/SKILL.md b/apps/docs/skills/github-tools-agents/SKILL.md index 171c3e3..ca63f24 100644 --- a/apps/docs/skills/github-tools-agents/SKILL.md +++ b/apps/docs/skills/github-tools-agents/SKILL.md @@ -110,22 +110,22 @@ See `./references/eve-agents.md` and `/deprecated/eve`. | Preset | Purpose | |--------|---------| -| `code-review` | PRs, commits, files, review comments | +| `code-review` | PRs, commits, files, review comments, review threads (list/reply/resolve) | | `issue-triage` | Issues via getIssueContext, comments, reactions, create/close, assignees | | `repo-explorer` | Read-only + search + discussions/gists/workflows reads | -| `ci-ops` | Actions workflows, runs, trigger/cancel/rerun | +| `ci-ops` | Actions workflows, runs, job logs, trigger/cancel/rerun | | `security-audit` | Vulnerability scanning, risk reporting | | `release-manager` | Changelog generation, release cutting | | `discussion-moderator` | Discussions list/get/comment plus light issue context | | `notification-inbox` | User notification triage (needs Notifications PAT) | -| `pr-author` | Branches, file edits, open/update PRs | -| `maintainer` | All 79 tools | +| `pr-author` | Branches (create/delete), file edits, open/update PRs, respond to review threads | +| `maintainer` | All 84 tools | Array presets merge: `preset: ['code-review', 'issue-triage']`. Start with the smallest preset that fits; use `maintainer` when you need the full catalog. Multi-role: manager + sub-agents each with one preset. ## Working context -Pass `context: { owner, repo, pullNumber?, issueNumber?, ref? }` to `createGithubTools` / `createGithubAgent` / `createDurableGithubAgent` to default those fields on tool inputs and inject them into the agent system prompt. Prefer composite tools (`getPullRequestContext`, `getIssueContext`, `getReleaseContext`, `getCiFailureContext`) for multi-part reads — call follow-up reads in the same step when possible. Diff patches are omitted by default — set `includePatch: true` (optionally with `filenames`) when you need specific diffs. Bodies are truncated by default (`detail: 'summary'`). `getIssueContext` returns `labelNames` (strings) rather than full label objects. Prefer `getFileContent` with `startLine`/`endLine` or `maxLines` for large files. Object-shaped execute results include `rateLimit` (`remaining` / `limit` / `reset` / `resource`); it is stripped from the model-facing output. Array-shaped list tools do not carry it. On 403/429 the error text includes remaining/reset. +Pass `context: { owner, repo, pullNumber?, issueNumber?, ref? }` to `createGithubTools` / `createGithubAgent` / `createDurableGithubAgent` to default those fields on tool inputs and inject them into the agent system prompt. Prefer composite tools (`getPullRequestContext`, `getIssueContext`, `getReleaseContext`, `getCiFailureContext`) for multi-part reads — call follow-up reads in the same step when possible. Diff patches are omitted by default — set `includePatch: true` (optionally with `filenames`) when you need specific diffs. Bodies are truncated by default (`detail: 'summary'`). `getIssueContext` returns `labelNames` (strings) rather than full label objects. Prefer `getFileContent` with `startLine`/`endLine` or `maxLines` for large files. `getWorkflowJobLogs` returns the last 200 log lines with timestamps stripped — raise `maxLines` (up to 2000) only when needed. `listPullRequestReviewThreads` returns unresolved threads only by default with truncated comment bodies. Object-shaped execute results include `rateLimit` (`remaining` / `limit` / `reset` / `resource`); it is stripped from the model-facing output. Array-shaped list tools do not carry it. On 403/429 the error text includes remaining/reset. ## Write safety diff --git a/packages/github-tools/README.md b/packages/github-tools/README.md index b2c8ec6..b029ac2 100644 --- a/packages/github-tools/README.md +++ b/packages/github-tools/README.md @@ -33,7 +33,7 @@ They all reach the GitHub API, but none of them were built as an agent's tool la | Production agents that must survive restarts and timeouts | [Durable Agents](#durable-agents-vercel-workflow-sdk) | | A GitHub, Slack, or Discord bot | [Chat SDK docs](https://github-tools.com/frameworks/chat-sdk) | -79 tools cover repositories, branches, pull requests, issues, reactions, discussions, notifications, commits, releases, checks and statuses, search, gists, and workflows. See the full [Tools Catalog](https://github-tools.com/api/tools-catalog). Write operations support granular approval control out of the box. +84 tools cover repositories, branches, pull requests, issues, reactions, discussions, notifications, commits, releases, checks and statuses, search, gists, and workflows. See the full [Tools Catalog](https://github-tools.com/api/tools-catalog). Write operations support granular approval control out of the box. ## Installation @@ -102,16 +102,16 @@ createGithubTools({ token, preset: ['code-review', 'issue-triage'] }) | Preset | Tools included | |---|---| -| `code-review` | `getPullRequest`, `listPullRequests`, `listPullRequestFiles`, `listPullRequestReviews`, `getPullRequestContext`, `getFileContent`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`, `getRepository`, `listBranches`, `searchCode`, `listCheckRuns`, `getCombinedStatus`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `requestReviewers` | +| `code-review` | `getPullRequest`, `listPullRequests`, `listPullRequestFiles`, `listPullRequestReviews`, `listPullRequestReviewThreads`, `getPullRequestContext`, `getFileContent`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`, `getRepository`, `listBranches`, `searchCode`, `listCheckRuns`, `getCombinedStatus`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `replyToReviewComment`, `resolveReviewThread`, `requestReviewers` | | `issue-triage` | `listIssues`, `getIssueContext`, `listIssueComments`, `createIssue`, `addIssueComment`, `updateIssueComment`, `deleteIssueComment`, `closeIssue`, `updateIssue`, `addLabels`, `removeLabel`, `createLabel`, `updateLabel`, `deleteLabel`, `addAssignees`, `removeAssignees`, `listIssueReactions`, `addIssueReaction`, `listCommentReactions`, `addCommentReaction`, `getRepository`, `searchRepositories`, `searchCode`, `searchIssues` | | `repo-explorer` | All read-only tools including discussions, gists, workflows, checks/statuses, and releases (no write operations) | -| `ci-ops` | `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `listCheckRuns`, `getCombinedStatus`, `getCiFailureContext`, `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun`, `getRepository`, `listBranches`, `listCommits`, `getCommit` | -| `security-audit` | Read-only exploration (`getFileContent`, `getRepositoryTree`, `searchCode`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`), PR and CI visibility, plus `createIssue`, `addIssueComment`, `addLabels` to report findings (no destructive writes) | +| `ci-ops` | `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `getWorkflowJobLogs`, `listCheckRuns`, `getCombinedStatus`, `getCiFailureContext`, `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun`, `getRepository`, `listBranches`, `listCommits`, `getCommit` | +| `security-audit` | Read-only exploration (`getFileContent`, `getRepositoryTree`, `searchCode`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`), PR and CI visibility (including `getWorkflowJobLogs`), plus `createIssue`, `addIssueComment`, `addLabels` to report findings (no destructive writes) | | `release-manager` | `listReleases`, `getLatestRelease`, `getRelease`, `getReleaseContext`, `createRelease`, `updateRelease`, `deleteRelease`, `compareCommits`, `listCommits`, `getCommit`, `listWorkflowRuns`, `getWorkflowRun`, `listPullRequests`, `getPullRequest`, `getRepository`, `listBranches` | | `discussion-moderator` | `listDiscussions`, `getDiscussion`, `addDiscussionComment`, `getRepository`, `searchIssues`, `getIssueContext`, `addIssueComment` | | `notification-inbox` | `listNotifications`, `markNotificationRead`, `getIssue`, `getPullRequest`, `getRepository` (requires a Notifications PAT) | -| `pr-author` | `getRepository`, `listBranches`, `getFileContent`, `createBranch`, `createOrUpdateFile`, `createPullRequest`, `updatePullRequest`, `getPullRequest`, `listPullRequestFiles`, `compareCommits`, `getCommit` | -| `maintainer` | All 79 tools | +| `pr-author` | `getRepository`, `listBranches`, `getFileContent`, `createBranch`, `deleteBranch`, `createOrUpdateFile`, `createPullRequest`, `updatePullRequest`, `getPullRequest`, `listPullRequestFiles`, `listPullRequestReviewThreads`, `replyToReviewComment`, `resolveReviewThread`, `compareCommits`, `getCommit` | +| `maintainer` | All 84 tools | Start with the smallest preset that fits. Use `maintainer` or omit `preset` when you need the full catalog. Full breakdown: [Tools Catalog](https://github-tools.com/api/tools-catalog). @@ -158,7 +158,7 @@ createGithubTools({ }) ``` -Write tools: `createBranch`, `forkRepository`, `createRepository`, `createOrUpdateFile`, `createPullRequest`, `mergePullRequest`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `requestReviewers`, `createIssue`, `addIssueComment`, `updateIssueComment`, `deleteIssueComment`, `closeIssue`, `updateIssue`, `addLabels`, `removeLabel`, `createLabel`, `updateLabel`, `deleteLabel`, `addAssignees`, `removeAssignees`, `addIssueReaction`, `addCommentReaction`, `addDiscussionComment`, `markNotificationRead`, `createGist`, `updateGist`, `deleteGist`, `createGistComment`, `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun`, `createRelease`, `updateRelease`, `deleteRelease`. +Write tools: `createBranch`, `deleteBranch`, `forkRepository`, `createRepository`, `createOrUpdateFile`, `createPullRequest`, `mergePullRequest`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `replyToReviewComment`, `resolveReviewThread`, `requestReviewers`, `createIssue`, `addIssueComment`, `updateIssueComment`, `deleteIssueComment`, `closeIssue`, `updateIssue`, `addLabels`, `removeLabel`, `createLabel`, `updateLabel`, `deleteLabel`, `addAssignees`, `removeAssignees`, `addIssueReaction`, `addCommentReaction`, `addDiscussionComment`, `markNotificationRead`, `createGist`, `updateGist`, `deleteGist`, `createGistComment`, `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun`, `createRelease`, `updateRelease`, `deleteRelease`. All other tools are read-only and never require approval. @@ -445,7 +445,7 @@ eve replays completed steps but re-runs steps interrupted mid-execution. Write t | `addIssueReaction`, `addCommentReaction` | Natural: GitHub returns the existing reaction when the user already reacted with the same content | | `markNotificationRead` | Natural when the thread is already read | | `updateIssue`, `updatePullRequest`, `updateRelease`, `updateIssueComment`, `updatePullRequestComment` | **Not** idempotent: each call applies a new revision | -| `deleteIssueComment`, `deletePullRequestComment`, `deleteRelease` | **Not** idempotent: deleting an already-deleted resource returns 404 from GitHub | +| `deleteIssueComment`, `deletePullRequestComment`, `deleteRelease`, `deleteBranch` | **Not** idempotent: deleting an already-deleted resource fails on GitHub | | `addIssueComment`, `addDiscussionComment`, `createIssue`, `mergePullRequest`, `createRelease`, … | **Not** idempotent: each call creates new side effects | Gate non-idempotent writes behind `always()` or `once()` where replay safety matters. @@ -482,6 +482,7 @@ List tools (`listCommits`, `listPullRequests`, `listIssues`, `listWorkflowRuns`, | `getFileContent` | Read a file or directory listing (prefer `startLine`/`endLine` or `maxLines` for large files) | | `getRepositoryTree` | List the file and directory structure at a given ref | | `createBranch` | Create a new branch from an existing branch or commit SHA | +| `deleteBranch` | Permanently delete a branch | | `forkRepository` | Fork a repository to a user or organization | | `createRepository` | Create a new repository for a user or organization | | `createOrUpdateFile` | Create or update a file and commit it | @@ -494,6 +495,7 @@ List tools (`listCommits`, `listPullRequests`, `listIssues`, `listWorkflowRuns`, | `getPullRequest` | Get a PR's full details (diff stats, body, merge status; body truncated by default) | | `listPullRequestFiles` | List files changed in a PR (patches omitted by default; set `includePatch` / `filenames` for diffs) | | `listPullRequestReviews` | List reviews on a PR (approvals, change requests, comments) | +| `listPullRequestReviewThreads` | List review threads with comments, resolution state, and reply/resolve IDs (unresolved only by default) | | `getPullRequestContext` | Fetch PR details plus files, reviews, and optional CI checks in one call | | `createPullRequest` | Open a new PR | | `mergePullRequest` | Merge a PR (merge, squash, or rebase) | @@ -502,6 +504,8 @@ List tools (`listCommits`, `listPullRequests`, `listIssues`, `listWorkflowRuns`, | `updatePullRequestComment` | Edit the body of a PR comment | | `deletePullRequestComment` | Permanently delete a PR comment | | `createPullRequestReview` | Submit a formal review (approve, request changes, or comment) with inline comments | +| `replyToReviewComment` | Reply to a review comment in its thread | +| `resolveReviewThread` | Mark a review thread as resolved | | `requestReviewers` | Request reviews from users or teams on a PR | ### Issues @@ -573,6 +577,7 @@ Pull request conversations share the issue numbering, so the issue-level tools w | `listWorkflowRuns` | List workflow runs filtered by workflow, branch, status, or event | | `getWorkflowRun` | Get a workflow run's status, timing, and trigger info | | `listWorkflowJobs` | List jobs in a workflow run with step-level status | +| `getWorkflowJobLogs` | Read a job's log output (last 200 lines by default, timestamps stripped) | | `triggerWorkflow` | Trigger a workflow via workflow_dispatch event | | `cancelWorkflowRun` | Cancel an in-progress workflow run | | `rerunWorkflowRun` | Re-run a workflow run, optionally only failed jobs | @@ -626,10 +631,10 @@ Create one at **GitHub → Settings → Developer settings → Personal access t |---|---|---| | **Metadata** | Read-only | Always required (auto-included) | | **Contents** | Read-only | `getRepository`, `listBranches`, `getFileContent`, `getRepositoryTree`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`, `listReleases`, `getLatestRelease`, `getRelease`, `getReleaseContext` | -| **Contents** | Read and write | `createBranch`, `createOrUpdateFile`, `createRelease`, `updateRelease`, `deleteRelease` | +| **Contents** | Read and write | `createBranch`, `deleteBranch`, `createOrUpdateFile`, `createRelease`, `updateRelease`, `deleteRelease` | | **Administration** | Read and write | `forkRepository`, `createRepository` | -| **Pull requests** | Read-only | `listPullRequests`, `getPullRequest`, `listPullRequestFiles`, `listPullRequestReviews`, `getPullRequestContext` | -| **Pull requests** | Read and write | `createPullRequest`, `mergePullRequest`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `requestReviewers` | +| **Pull requests** | Read-only | `listPullRequests`, `getPullRequest`, `listPullRequestFiles`, `listPullRequestReviews`, `listPullRequestReviewThreads`, `getPullRequestContext` | +| **Pull requests** | Read and write | `createPullRequest`, `mergePullRequest`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, `deletePullRequestComment`, `createPullRequestReview`, `replyToReviewComment`, `resolveReviewThread`, `requestReviewers` | | **Issues** | Read-only | `listIssues`, `getIssue`, `getIssueContext`, `listIssueComments`, `listLabels`, `listIssueReactions`, `listCommentReactions` | | **Issues** | Read and write | `createIssue`, `addIssueComment`, `updateIssueComment`, `deleteIssueComment`, `closeIssue`, `updateIssue`, `addLabels`, `removeLabel`, `createLabel`, `updateLabel`, `deleteLabel`, `addAssignees`, `removeAssignees`, `addIssueReaction`, `addCommentReaction` | | **Discussions** | Read-only | `listDiscussions`, `getDiscussion` | @@ -637,7 +642,7 @@ Create one at **GitHub → Settings → Developer settings → Personal access t | **Gists** | Read-only | `listGists`, `getGist`, `listGistComments` | | **Gists** | Read and write | `createGist`, `updateGist`, `deleteGist`, `createGistComment` | | **Notifications** (account) | Read and write | `listNotifications`, `markNotificationRead` | -| **Actions** | Read-only | `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `getCiFailureContext` | +| **Actions** | Read-only | `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `getWorkflowJobLogs`, `getCiFailureContext` | | **Actions** | Read and write | `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun` | | **Checks** | Read-only | `listCheckRuns`, `getCiFailureContext` | | **Commit statuses** | Read-only | `getCombinedStatus`, `getCiFailureContext` | diff --git a/packages/github-tools/src/agents.ts b/packages/github-tools/src/agents.ts index 6f42222..b45b098 100644 --- a/packages/github-tools/src/agents.ts +++ b/packages/github-tools/src/agents.ts @@ -25,6 +25,7 @@ When reviewing a PR: - Use getBlame then getCommit(includePatch true) only when line history matters - Check for bugs, logic errors, and edge cases; be constructive - Use createPullRequestReview for formal reviews when asked +- Use listPullRequestReviewThreads (unresolved by default) to see open feedback; replyToReviewComment to answer in a thread and resolveReviewThread once it is addressed - Use updatePullRequest to change title, body, base branch, or draft status; addPullRequestComment / updatePullRequestComment / deletePullRequestComment to manage comments ${SHARED_RULES}`, @@ -47,6 +48,7 @@ ${SHARED_RULES}`, When working with workflows: - Prefer getCiFailureContext first when diagnosing a failing ref +- Read the failing job's output with getWorkflowJobLogs (the default 200-line tail is usually enough; raise maxLines only when the error is higher up) - Use listCheckRuns / getCombinedStatus for narrower follow-ups - Inspect job steps to find the failing step; confirm before cancel/re-run - Trigger workflow_dispatch with the correct inputs and branch when asked @@ -115,6 +117,8 @@ When opening a PR: - createBranch from a sensible base, then createOrUpdateFile for the change set - createPullRequest with a clear title and body; updatePullRequest if the draft or description needs a fix - Use listPullRequestFiles / compareCommits / getCommit to verify what will land +- Address review feedback: listPullRequestReviewThreads for open threads, replyToReviewComment to answer, resolveReviewThread once fixed +- Use deleteBranch to clean up a merged or abandoned branch when asked - Stay scoped to authoring — you do not review, merge, or manage issues ${SHARED_RULES}`, diff --git a/packages/github-tools/src/connect/scopes.ts b/packages/github-tools/src/connect/scopes.ts index 8101def..eb3b806 100644 --- a/packages/github-tools/src/connect/scopes.ts +++ b/packages/github-tools/src/connect/scopes.ts @@ -166,6 +166,7 @@ export const TOOL_CONNECT_SCOPES = { getFileContent: CONTENTS_READ, getRepositoryTree: CONTENTS_READ, createBranch: CONTENTS_WRITE, + deleteBranch: CONTENTS_WRITE, forkRepository: CONTENTS_READ, createRepository: ADMIN, createOrUpdateFile: CONTENTS_WRITE, @@ -180,7 +181,10 @@ export const TOOL_CONNECT_SCOPES = { deletePullRequestComment: PR_WRITE, listPullRequestFiles: PR_READ, listPullRequestReviews: PR_READ, + listPullRequestReviewThreads: PR_READ, createPullRequestReview: PR_WRITE, + replyToReviewComment: PR_WRITE, + resolveReviewThread: PR_WRITE, requestReviewers: PR_WRITE, getPullRequestContext: PR_CONTEXT, @@ -224,6 +228,7 @@ export const TOOL_CONNECT_SCOPES = { listWorkflowRuns: ACTIONS_READ, getWorkflowRun: ACTIONS_READ, listWorkflowJobs: ACTIONS_READ, + getWorkflowJobLogs: ACTIONS_READ, triggerWorkflow: ACTIONS_WRITE, cancelWorkflowRun: ACTIONS_WRITE, rerunWorkflowRun: ACTIONS_WRITE, diff --git a/packages/github-tools/src/core/presets.ts b/packages/github-tools/src/core/presets.ts index d7fbfc7..eb55348 100644 --- a/packages/github-tools/src/core/presets.ts +++ b/packages/github-tools/src/core/presets.ts @@ -9,17 +9,17 @@ export const PRESET_TOOLS = { /** * **Code review** — review pull requests and submit feedback. * - * Tools: `getPullRequest`, `listPullRequests`, `listPullRequestFiles`, `listPullRequestReviews`, `getPullRequestContext`, + * Tools: `getPullRequest`, `listPullRequests`, `listPullRequestFiles`, `listPullRequestReviews`, `listPullRequestReviewThreads`, `getPullRequestContext`, * `getFileContent`, `listCommits`, `getCommit`, `getBlame`, `compareCommits`, `getRepository`, `listBranches`, * `searchCode`, `listCheckRuns`, `getCombinedStatus`, `updatePullRequest`, `addPullRequestComment`, `updatePullRequestComment`, - * `deletePullRequestComment`, `createPullRequestReview`, `requestReviewers`. + * `deletePullRequestComment`, `createPullRequestReview`, `replyToReviewComment`, `resolveReviewThread`, `requestReviewers`. * * Agent prompt: optimized for thorough PR review with inline feedback. */ 'code-review': [ - 'getPullRequest', 'listPullRequests', 'listPullRequestFiles', 'listPullRequestReviews', 'getPullRequestContext', 'getFileContent', 'listCommits', 'getCommit', 'getBlame', 'compareCommits', + 'getPullRequest', 'listPullRequests', 'listPullRequestFiles', 'listPullRequestReviews', 'listPullRequestReviewThreads', 'getPullRequestContext', 'getFileContent', 'listCommits', 'getCommit', 'getBlame', 'compareCommits', 'getRepository', 'listBranches', 'searchCode', 'listCheckRuns', 'getCombinedStatus', - 'updatePullRequest', 'addPullRequestComment', 'updatePullRequestComment', 'deletePullRequestComment', 'createPullRequestReview', 'requestReviewers', + 'updatePullRequest', 'addPullRequestComment', 'updatePullRequestComment', 'deletePullRequestComment', 'createPullRequestReview', 'replyToReviewComment', 'resolveReviewThread', 'requestReviewers', ], /** * **Issue triage** — manage and organize GitHub issues. @@ -46,15 +46,16 @@ export const PRESET_TOOLS = { * **CI operations** — monitor and manage GitHub Actions workflows. * * Tools: `getRepository`, `listBranches`, `listCommits`, `getCommit`, - * `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `listCheckRuns`, `getCombinedStatus`, `getCiFailureContext`, + * `listWorkflows`, `listWorkflowRuns`, `getWorkflowRun`, `listWorkflowJobs`, `getWorkflowJobLogs`, `listCheckRuns`, `getCombinedStatus`, `getCiFailureContext`, * `triggerWorkflow`, `cancelWorkflowRun`, `rerunWorkflowRun`. * + * Diagnose a failure with `getCiFailureContext` first, then read the failing job's output with `getWorkflowJobLogs`. * Agent prompt: optimized for diagnosing CI failures and managing workflow runs. */ 'ci-ops': [ 'getRepository', 'listBranches', 'listCommits', 'getCommit', - 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', + 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'getWorkflowJobLogs', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', 'triggerWorkflow', 'cancelWorkflowRun', 'rerunWorkflowRun', ], /** @@ -67,14 +68,14 @@ export const PRESET_TOOLS = { */ 'repo-explorer': [ 'getRepository', 'listBranches', 'getFileContent', 'getRepositoryTree', - 'listPullRequests', 'getPullRequest', 'listPullRequestFiles', 'listPullRequestReviews', 'getPullRequestContext', + 'listPullRequests', 'getPullRequest', 'listPullRequestFiles', 'listPullRequestReviews', 'listPullRequestReviewThreads', 'getPullRequestContext', 'listIssues', 'getIssue', 'getIssueContext', 'listIssueComments', 'listDiscussions', 'getDiscussion', 'listLabels', 'listCommits', 'getCommit', 'getBlame', 'compareCommits', 'searchCode', 'searchRepositories', 'searchIssues', 'listGists', 'getGist', 'listGistComments', - 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', + 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'getWorkflowJobLogs', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', 'listReleases', 'getLatestRelease', 'getRelease', 'getReleaseContext', ], /** @@ -83,7 +84,8 @@ export const PRESET_TOOLS = { * Tools: read-only exploration (`getFileContent`, `getRepositoryTree`, `searchCode`, `searchIssues`, `listCommits`, `getCommit`, * `getBlame`, `compareCommits`), PR and CI visibility (`listPullRequests`, `getPullRequest`, `listPullRequestFiles`, * `getPullRequestContext`, `listCheckRuns`, `getCombinedStatus`, `getCiFailureContext`, `listWorkflows`, `listWorkflowRuns`, - * `getWorkflowRun`, `listWorkflowJobs`), plus `createIssue`, `addIssueComment`, and `addLabels` to report findings. + * `getWorkflowRun`, `listWorkflowJobs`, `getWorkflowJobLogs` — logs can leak secrets, so auditing them matters), + * plus `createIssue`, `addIssueComment`, and `addLabels` to report findings. * No destructive writes. * * Agent prompt: optimized for finding and reporting security risks without making changes. @@ -94,7 +96,7 @@ export const PRESET_TOOLS = { 'searchCode', 'searchRepositories', 'searchIssues', 'listPullRequests', 'getPullRequest', 'listPullRequestFiles', 'getPullRequestContext', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', - 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', + 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'getWorkflowJobLogs', 'listIssues', 'getIssue', 'getIssueContext', 'createIssue', 'addIssueComment', 'addLabels', ], /** @@ -141,15 +143,18 @@ export const PRESET_TOOLS = { /** * **PR author** — open pull requests without the full write surface. * - * Tools: `getRepository`, `listBranches`, `getFileContent`, `createBranch`, `createOrUpdateFile`, + * Tools: `getRepository`, `listBranches`, `getFileContent`, `createBranch`, `deleteBranch`, `createOrUpdateFile`, * `createPullRequest`, `updatePullRequest`, `getPullRequest`, `listPullRequestFiles`, + * `listPullRequestReviewThreads`, `replyToReviewComment`, `resolveReviewThread`, * `compareCommits`, `getCommit`. * + * Respond to review feedback with `listPullRequestReviewThreads` → `replyToReviewComment` → `resolveReviewThread`. * Agent prompt: optimized for branching, editing files, and opening focused PRs. */ 'pr-author': [ - 'getRepository', 'listBranches', 'getFileContent', 'createBranch', 'createOrUpdateFile', + 'getRepository', 'listBranches', 'getFileContent', 'createBranch', 'deleteBranch', 'createOrUpdateFile', 'createPullRequest', 'updatePullRequest', 'getPullRequest', 'listPullRequestFiles', + 'listPullRequestReviewThreads', 'replyToReviewComment', 'resolveReviewThread', 'compareCommits', 'getCommit', ], /** @@ -160,8 +165,8 @@ export const PRESET_TOOLS = { * Agent prompt: optimized for day-to-day repo maintenance with careful write operations. */ 'maintainer': [ - 'getRepository', 'listBranches', 'getFileContent', 'getRepositoryTree', 'createBranch', 'forkRepository', 'createRepository', 'createOrUpdateFile', - 'listPullRequests', 'getPullRequest', 'listPullRequestFiles', 'listPullRequestReviews', 'getPullRequestContext', 'createPullRequest', 'mergePullRequest', 'updatePullRequest', 'addPullRequestComment', 'updatePullRequestComment', 'deletePullRequestComment', 'createPullRequestReview', 'requestReviewers', + 'getRepository', 'listBranches', 'getFileContent', 'getRepositoryTree', 'createBranch', 'deleteBranch', 'forkRepository', 'createRepository', 'createOrUpdateFile', + 'listPullRequests', 'getPullRequest', 'listPullRequestFiles', 'listPullRequestReviews', 'listPullRequestReviewThreads', 'getPullRequestContext', 'createPullRequest', 'mergePullRequest', 'updatePullRequest', 'addPullRequestComment', 'updatePullRequestComment', 'deletePullRequestComment', 'createPullRequestReview', 'replyToReviewComment', 'resolveReviewThread', 'requestReviewers', 'listIssues', 'getIssue', 'getIssueContext', 'listIssueComments', 'createIssue', 'addIssueComment', 'updateIssueComment', 'deleteIssueComment', 'closeIssue', 'updateIssue', 'listLabels', 'addLabels', 'removeLabel', 'createLabel', 'updateLabel', 'deleteLabel', 'addAssignees', 'removeAssignees', 'listIssueReactions', 'addIssueReaction', 'listCommentReactions', 'addCommentReaction', @@ -170,7 +175,7 @@ export const PRESET_TOOLS = { 'listCommits', 'getCommit', 'getBlame', 'compareCommits', 'searchCode', 'searchRepositories', 'searchIssues', 'listGists', 'getGist', 'listGistComments', 'createGist', 'updateGist', 'deleteGist', 'createGistComment', - 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'triggerWorkflow', 'cancelWorkflowRun', 'rerunWorkflowRun', + 'listWorkflows', 'listWorkflowRuns', 'getWorkflowRun', 'listWorkflowJobs', 'getWorkflowJobLogs', 'triggerWorkflow', 'cancelWorkflowRun', 'rerunWorkflowRun', 'listCheckRuns', 'getCombinedStatus', 'getCiFailureContext', 'listReleases', 'getLatestRelease', 'getRelease', 'getReleaseContext', 'createRelease', 'updateRelease', 'deleteRelease', ], diff --git a/packages/github-tools/src/core/pull-requests.ts b/packages/github-tools/src/core/pull-requests.ts index f3195f5..9df5435 100644 --- a/packages/github-tools/src/core/pull-requests.ts +++ b/packages/github-tools/src/core/pull-requests.ts @@ -353,6 +353,194 @@ export async function createPullRequestReviewCore({ token, owner, repo, pullNumb }) } +export const listPullRequestReviewThreadsInputSchema = z.object({ + owner: z.string().describe('Repository owner'), + repo: z.string().describe('Repository name'), + pullNumber: z.number().describe('Pull request number'), + status: z.enum(['unresolved', 'all']).optional().default('unresolved').describe('unresolved returns only threads awaiting action (default, saves tokens); all includes resolved threads'), + detail: detailSchema, + perPage: z.number().int().positive().max(50).optional().default(30).describe('Number of threads to fetch per call (max 50)'), + after: z.string().optional().describe('Cursor from a previous call (endCursor) to fetch the next page of threads'), +}) + +export const listPullRequestReviewThreadsDescription = 'List review threads on a pull request with their comments, resolution state, and the IDs needed to reply (commentId) or resolve (threadId). Unresolved threads only by default (GitHub GraphQL API)' + +const REVIEW_THREADS_QUERY = /* GraphQL */ ` + query ReviewThreads($owner: String!, $name: String!, $number: Int!, $first: Int!, $after: String) { + repository(owner: $owner, name: $name) { + pullRequest(number: $number) { + reviewThreads(first: $first, after: $after) { + totalCount + pageInfo { + hasNextPage + endCursor + } + nodes { + id + isResolved + isOutdated + path + line + startLine + resolvedBy { + login + } + comments(first: 30) { + totalCount + nodes { + databaseId + body + createdAt + author { + login + } + } + } + } + } + } + } + } +` + +type ReviewThreadsQueryData = { + repository: { + pullRequest: { + reviewThreads: { + totalCount: number + pageInfo: { hasNextPage: boolean, endCursor: string | null } + nodes: Array<{ + id: string + isResolved: boolean + isOutdated: boolean + path: string | null + line: number | null + startLine: number | null + resolvedBy: { login: string } | null + comments: { + totalCount: number + nodes: Array<{ + databaseId: number | null + body: string + createdAt: string + author: { login: string } | null + }> + } + }> + } | null + } | null + } | null +} + +export async function listPullRequestReviewThreadsCore({ token, owner, repo, pullNumber, status, detail, perPage, after }: { token: string, owner: string, repo: string, pullNumber: number, status: 'unresolved' | 'all', detail: DetailLevel, perPage: number, after?: string }) { + return withOctokit(token, async (octokit) => { + const data = (await octokit.graphql(REVIEW_THREADS_QUERY, { + owner, + name: repo, + number: pullNumber, + first: perPage, + after, + })) as ReviewThreadsQueryData + + if (!data.repository) { + return { error: `Repository not found: ${owner}/${repo}` } + } + const reviewThreads = data.repository.pullRequest?.reviewThreads + if (!reviewThreads) { + return { error: `Pull request not found: ${owner}/${repo}#${pullNumber}` } + } + + const threads = reviewThreads.nodes + .filter(thread => status === 'all' || !thread.isResolved) + .map(thread => ({ + threadId: thread.id, + isResolved: thread.isResolved, + isOutdated: thread.isOutdated, + path: thread.path, + line: thread.line, + startLine: thread.startLine, + resolvedBy: thread.resolvedBy?.login ?? null, + commentCount: thread.comments.totalCount, + comments: thread.comments.nodes.map(comment => ({ + commentId: comment.databaseId, + author: comment.author?.login ?? null, + body: applyDetailBody(comment.body, detail), + createdAt: comment.createdAt, + })), + })) + + return { + // Total across all threads on the PR, before the `status` filter. + totalCount: reviewThreads.totalCount, + returnedCount: threads.length, + hasNextPage: reviewThreads.pageInfo.hasNextPage, + endCursor: reviewThreads.pageInfo.endCursor, + threads, + } + }) +} + +export const replyToReviewCommentInputSchema = z.object({ + owner: z.string().describe('Repository owner'), + repo: z.string().describe('Repository name'), + pullNumber: z.number().describe('Pull request number'), + commentId: z.number().describe('commentId of the thread\'s first comment, from listPullRequestReviewThreads'), + body: z.string().describe('Reply text (supports Markdown)'), +}) + +export const replyToReviewCommentDescription = 'Reply to a pull request review comment, adding a message to its review thread' + +/** Not idempotent — each call posts a new reply. */ +export async function replyToReviewCommentCore({ token, owner, repo, pullNumber, commentId, body }: { token: string, owner: string, repo: string, pullNumber: number, commentId: number, body: string }) { + return withOctokit(token, async (octokit) => { + const { data } = await octokit.rest.pulls.createReplyForReviewComment({ + owner, + repo, + pull_number: pullNumber, + comment_id: commentId, + body, + }) + return { + id: data.id, + url: data.html_url, + author: data.user?.login, + createdAt: data.created_at, + } + }) +} + +export const resolveReviewThreadInputSchema = z.object({ + threadId: z.string().describe('Review thread ID (threadId from listPullRequestReviewThreads)'), +}) + +export const resolveReviewThreadDescription = 'Mark a pull request review thread as resolved' + +const RESOLVE_THREAD_MUTATION = /* GraphQL */ ` + mutation ResolveReviewThread($threadId: ID!) { + resolveReviewThread(input: { threadId: $threadId }) { + thread { + id + isResolved + } + } + } +` + +/** Idempotent — resolving an already-resolved thread is a no-op on GitHub. */ +export async function resolveReviewThreadCore({ token, threadId }: { token: string, threadId: string }) { + return withOctokit(token, async (octokit) => { + const data = (await octokit.graphql(RESOLVE_THREAD_MUTATION, { threadId })) as { + resolveReviewThread: { thread: { id: string, isResolved: boolean } | null } | null + } + + const thread = data.resolveReviewThread?.thread + if (!thread) { + return { error: `Review thread not found: ${threadId}` } + } + return { threadId: thread.id, isResolved: thread.isResolved } + }) +} + export const requestReviewersInputSchema = z.object({ owner: z.string().describe('Repository owner'), repo: z.string().describe('Repository name'), diff --git a/packages/github-tools/src/core/repository.ts b/packages/github-tools/src/core/repository.ts index b4302c5..eaa7be8 100644 --- a/packages/github-tools/src/core/repository.ts +++ b/packages/github-tools/src/core/repository.ts @@ -255,6 +255,22 @@ export async function createBranchCore({ token, owner, repo, branch, from }: { t }) } +export const deleteBranchInputSchema = z.object({ + owner: z.string().describe('Repository owner'), + repo: z.string().describe('Repository name'), + branch: z.string().describe('Branch name to delete (without the refs/heads/ prefix)'), +}) + +export const deleteBranchDescription = 'Delete a branch from a GitHub repository permanently' + +/** Not idempotent — deleting a branch that no longer exists fails with a 422. */ +export async function deleteBranchCore({ token, owner, repo, branch }: { token: string, owner: string, repo: string, branch: string }) { + return withOctokit(token, async (octokit) => { + await octokit.rest.git.deleteRef({ owner, repo, ref: `heads/${branch}` }) + return { deleted: true, branch } + }) +} + export const forkRepositoryInputSchema = z.object({ owner: z.string().describe('Repository owner to fork from'), repo: z.string().describe('Repository name to fork'), diff --git a/packages/github-tools/src/core/tool-names.ts b/packages/github-tools/src/core/tool-names.ts index 37e7fb7..6e7db75 100644 --- a/packages/github-tools/src/core/tool-names.ts +++ b/packages/github-tools/src/core/tool-names.ts @@ -13,6 +13,8 @@ export const GITHUB_TOOL_NAMES = { getRepositoryTree: 'getRepositoryTree', /** Create a new branch in a GitHub repository from an existing branch or commit SHA. Requires approval by default. */ createBranch: 'createBranch', + /** Delete a branch from a GitHub repository permanently. Requires approval by default. */ + deleteBranch: 'deleteBranch', /** Fork a GitHub repository to the authenticated user account or a specified organization. Requires approval by default. */ forkRepository: 'forkRepository', /** Create a new GitHub repository for the authenticated user or a specified organization. Requires approval by default. */ @@ -41,6 +43,12 @@ export const GITHUB_TOOL_NAMES = { listPullRequestReviews: 'listPullRequestReviews', /** Submit a pull request review — approve, request changes, or comment with optional inline comments on specific lines. Requires approval by default. */ createPullRequestReview: 'createPullRequestReview', + /** List review threads on a pull request with comments, resolution state, and the IDs needed to reply or resolve. Unresolved only by default. */ + listPullRequestReviewThreads: 'listPullRequestReviewThreads', + /** Reply to a pull request review comment in its review thread. Requires approval by default. */ + replyToReviewComment: 'replyToReviewComment', + /** Mark a pull request review thread as resolved. Requires approval by default. */ + resolveReviewThread: 'resolveReviewThread', /** Request reviews from users or teams on a pull request. Requires approval by default. */ requestReviewers: 'requestReviewers', /** Fetch pull request details plus files, reviews, and optional CI checks in one call. */ @@ -117,6 +125,8 @@ export const GITHUB_TOOL_NAMES = { getWorkflowRun: 'getWorkflowRun', /** List jobs for a workflow run, including step-level status and timing. */ listWorkflowJobs: 'listWorkflowJobs', + /** Get the log output of a workflow job to diagnose failures. Returns the tail (default 200 lines) with timestamps stripped. */ + getWorkflowJobLogs: 'getWorkflowJobLogs', /** Trigger a workflow via workflow_dispatch event. Requires approval by default. */ triggerWorkflow: 'triggerWorkflow', /** Cancel an in-progress workflow run. Requires approval by default. */ diff --git a/packages/github-tools/src/core/workflows.test.ts b/packages/github-tools/src/core/workflows.test.ts new file mode 100644 index 0000000..36c74c1 --- /dev/null +++ b/packages/github-tools/src/core/workflows.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest' +import { shapeJobLog } from './workflows' + +const line = (n: number, text: string) => `2026-08-28T12:00:${String(n % 60).padStart(2, '0')}.1234567Z ${text}` + +describe('shapeJobLog', () => { + it('strips the ISO timestamp prefix from every line', () => { + const raw = [line(1, 'Set up job'), line(2, '##[error]Process completed with exit code 1.')].join('\n') + expect(shapeJobLog(raw, 200)).toEqual({ + totalLines: 2, + returnedLines: 2, + omittedLines: 0, + log: 'Set up job\n##[error]Process completed with exit code 1.', + }) + }) + + it('returns only the last maxLines lines and reports what was omitted', () => { + const raw = Array.from({ length: 500 }, (_, i) => line(i, `step output ${i}`)).join('\n') + const shaped = shapeJobLog(raw, 200) + expect(shaped.totalLines).toBe(500) + expect(shaped.returnedLines).toBe(200) + expect(shaped.omittedLines).toBe(300) + expect(shaped.log.startsWith('step output 300')).toBe(true) + expect(shaped.log.endsWith('step output 499')).toBe(true) + }) + + it('normalizes CRLF and drops trailing newlines before counting', () => { + const raw = `${line(1, 'one')}\r\n${line(2, 'two')}\n\n` + expect(shapeJobLog(raw, 200).totalLines).toBe(2) + }) + + it('keeps lines without a timestamp prefix unchanged', () => { + expect(shapeJobLog('plain line', 200).log).toBe('plain line') + }) +}) diff --git a/packages/github-tools/src/core/workflows.ts b/packages/github-tools/src/core/workflows.ts index 34e6716..f0ab36a 100644 --- a/packages/github-tools/src/core/workflows.ts +++ b/packages/github-tools/src/core/workflows.ts @@ -144,6 +144,39 @@ export async function listWorkflowJobsCore({ token, owner, repo, runId, filter, }) } +export const getWorkflowJobLogsInputSchema = z.object({ + owner: z.string().describe('Repository owner'), + repo: z.string().describe('Repository name'), + jobId: z.number().describe('Workflow job ID (from listWorkflowJobs or getCiFailureContext)'), + maxLines: z.number().int().positive().max(2000).optional().default(200).describe('Return only the last N log lines (default 200, max 2000). Failures surface at the end of a log, so the tail is usually enough'), +}) + +export const getWorkflowJobLogsDescription = 'Get the log output of a workflow job to diagnose failures. Returns the last maxLines lines (default 200) with per-line timestamps stripped to save tokens' + +const LOG_TIMESTAMP_PREFIX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z / + +/** + * Tail-truncate a raw job log and strip the ISO timestamp GitHub prefixes on + * every line (~29 tokens-worth of noise per line the model never needs). + */ +export function shapeJobLog(raw: string, maxLines: number) { + const lines = raw.replace(/\r\n/g, '\n').replace(/\n+$/, '').split('\n') + const tail = lines.slice(-maxLines).map(line => line.replace(LOG_TIMESTAMP_PREFIX, '')) + return { + totalLines: lines.length, + returnedLines: tail.length, + omittedLines: lines.length - tail.length, + log: tail.join('\n'), + } +} + +export async function getWorkflowJobLogsCore({ token, owner, repo, jobId, maxLines }: { token: string, owner: string, repo: string, jobId: number, maxLines: number }) { + return withOctokit(token, async (octokit) => { + const { data } = await octokit.rest.actions.downloadJobLogsForWorkflowRun({ owner, repo, job_id: jobId }) + return { jobId, ...shapeJobLog(String(data), maxLines) } + }) +} + export const triggerWorkflowInputSchema = z.object({ owner: z.string().describe('Repository owner'), repo: z.string().describe('Repository name'), diff --git a/packages/github-tools/src/core/write-tools.ts b/packages/github-tools/src/core/write-tools.ts index 5eef4f7..822aead 100644 --- a/packages/github-tools/src/core/write-tools.ts +++ b/packages/github-tools/src/core/write-tools.ts @@ -5,6 +5,8 @@ export const GITHUB_WRITE_TOOLS = { /** Create a new branch in a GitHub repository from an existing branch or commit SHA. Requires approval by default. */ createBranch: 'createBranch', + /** Delete a branch from a GitHub repository permanently. Requires approval by default. */ + deleteBranch: 'deleteBranch', /** Fork a GitHub repository to the authenticated user account or a specified organization. Requires approval by default. */ forkRepository: 'forkRepository', /** Create a new GitHub repository for the authenticated user or a specified organization. Requires approval by default. */ @@ -25,6 +27,10 @@ export const GITHUB_WRITE_TOOLS = { deletePullRequestComment: 'deletePullRequestComment', /** Submit a pull request review with optional inline comments. Requires approval by default. */ createPullRequestReview: 'createPullRequestReview', + /** Reply to a pull request review comment in its review thread. Requires approval by default. */ + replyToReviewComment: 'replyToReviewComment', + /** Mark a pull request review thread as resolved. Requires approval by default. */ + resolveReviewThread: 'resolveReviewThread', /** Request reviews from users or teams on a pull request. Requires approval by default. */ requestReviewers: 'requestReviewers', /** Create a new issue in a GitHub repository. Requires approval by default. */ diff --git a/packages/github-tools/src/eve/registry.ts b/packages/github-tools/src/eve/registry.ts index b542302..de30f13 100644 --- a/packages/github-tools/src/eve/registry.ts +++ b/packages/github-tools/src/eve/registry.ts @@ -143,6 +143,13 @@ export function createToolRegistry(ctx: ToolBuildContext): ToolRegistryEntry[] { inputSchema: repository.createBranchInputSchema, execute: withToken(repository.createBranchCore, ctx), }, + { + name: 'deleteBranch', + writeTool: 'deleteBranch', + description: repository.deleteBranchDescription, + inputSchema: repository.deleteBranchInputSchema, + execute: withToken(repository.deleteBranchCore, ctx), + }, { name: 'forkRepository', writeTool: 'forkRepository', @@ -242,6 +249,26 @@ export function createToolRegistry(ctx: ToolBuildContext): ToolRegistryEntry[] { inputSchema: pullRequests.createPullRequestReviewInputSchema, execute: withToken(pullRequests.createPullRequestReviewCore, ctx), }, + { + name: 'listPullRequestReviewThreads', + description: pullRequests.listPullRequestReviewThreadsDescription, + inputSchema: pullRequests.listPullRequestReviewThreadsInputSchema, + execute: withToken(pullRequests.listPullRequestReviewThreadsCore, ctx), + }, + { + name: 'replyToReviewComment', + writeTool: 'replyToReviewComment', + description: pullRequests.replyToReviewCommentDescription, + inputSchema: pullRequests.replyToReviewCommentInputSchema, + execute: withToken(pullRequests.replyToReviewCommentCore, ctx), + }, + { + name: 'resolveReviewThread', + writeTool: 'resolveReviewThread', + description: pullRequests.resolveReviewThreadDescription, + inputSchema: pullRequests.resolveReviewThreadInputSchema, + execute: withToken(pullRequests.resolveReviewThreadCore, ctx), + }, { name: 'requestReviewers', writeTool: 'requestReviewers', @@ -549,6 +576,12 @@ export function createToolRegistry(ctx: ToolBuildContext): ToolRegistryEntry[] { inputSchema: workflows.listWorkflowJobsInputSchema, execute: withToken(workflows.listWorkflowJobsCore, ctx), }, + { + name: 'getWorkflowJobLogs', + description: workflows.getWorkflowJobLogsDescription, + inputSchema: workflows.getWorkflowJobLogsInputSchema, + execute: withToken(workflows.getWorkflowJobLogsCore, ctx), + }, { name: 'triggerWorkflow', writeTool: 'triggerWorkflow', diff --git a/packages/github-tools/src/index.ts b/packages/github-tools/src/index.ts index cb022a7..2a27419 100644 --- a/packages/github-tools/src/index.ts +++ b/packages/github-tools/src/index.ts @@ -1,6 +1,6 @@ import type { ToolSet } from 'ai' -import { getRepository, listBranches, getFileContent, getRepositoryTree, createBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository' -import { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, updatePullRequest, addPullRequestComment, updatePullRequestComment, deletePullRequestComment, listPullRequestFiles, listPullRequestReviews, createPullRequestReview, requestReviewers } from './tools/pull-requests' +import { getRepository, listBranches, getFileContent, getRepositoryTree, createBranch, deleteBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository' +import { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, updatePullRequest, addPullRequestComment, updatePullRequestComment, deletePullRequestComment, listPullRequestFiles, listPullRequestReviews, listPullRequestReviewThreads, createPullRequestReview, replyToReviewComment, resolveReviewThread, requestReviewers } from './tools/pull-requests' import { listIssues, getIssue, listIssueComments, createIssue, addIssueComment, updateIssueComment, deleteIssueComment, closeIssue, updateIssue, listLabels, addLabels, removeLabel, createLabel, updateLabel, deleteLabel, addAssignees, removeAssignees } from './tools/issues' import { listIssueReactions, addIssueReaction, listCommentReactions, addCommentReaction } from './tools/reactions' import { listDiscussions, getDiscussion, addDiscussionComment } from './tools/discussions' @@ -8,7 +8,7 @@ import { listNotifications, markNotificationRead } from './tools/notifications' import { searchCode, searchRepositories, searchIssues } from './tools/search' import { listCommits, getCommit, getBlame, compareCommits } from './tools/commits' import { listGists, getGist, listGistComments, createGist, updateGist, deleteGist, createGistComment } from './tools/gists' -import { listWorkflows, listWorkflowRuns, getWorkflowRun, listWorkflowJobs, triggerWorkflow, cancelWorkflowRun, rerunWorkflowRun } from './tools/workflows' +import { listWorkflows, listWorkflowRuns, getWorkflowRun, listWorkflowJobs, getWorkflowJobLogs, triggerWorkflow, cancelWorkflowRun, rerunWorkflowRun } from './tools/workflows' import { listCheckRuns, getCombinedStatus } from './tools/checks' import { listReleases, getLatestRelease, getRelease, createRelease, updateRelease, deleteRelease } from './tools/releases' import { getPullRequestContext, getIssueContext, getReleaseContext, getCiFailureContext } from './tools/bundles' @@ -131,6 +131,7 @@ export function createGithubTools({ getBlame: getBlame(resolveToken), compareCommits: compareCommits(resolveToken), createBranch: createBranch(resolveToken, approval('createBranch')), + deleteBranch: deleteBranch(resolveToken, approval('deleteBranch')), forkRepository: forkRepository(resolveToken, approval('forkRepository')), createRepository: createRepository(resolveToken, approval('createRepository')), createOrUpdateFile: createOrUpdateFile(resolveToken, { ...approval('createOrUpdateFile'), author, committer, coAuthors }), @@ -142,7 +143,10 @@ export function createGithubTools({ deletePullRequestComment: deletePullRequestComment(resolveToken, approval('deletePullRequestComment')), listPullRequestFiles: listPullRequestFiles(resolveToken), listPullRequestReviews: listPullRequestReviews(resolveToken), + listPullRequestReviewThreads: listPullRequestReviewThreads(resolveToken), createPullRequestReview: createPullRequestReview(resolveToken, approval('createPullRequestReview')), + replyToReviewComment: replyToReviewComment(resolveToken, approval('replyToReviewComment')), + resolveReviewThread: resolveReviewThread(resolveToken, approval('resolveReviewThread')), requestReviewers: requestReviewers(resolveToken, approval('requestReviewers')), getPullRequestContext: getPullRequestContext(resolveToken), getIssueContext: getIssueContext(resolveToken), @@ -181,6 +185,7 @@ export function createGithubTools({ listWorkflowRuns: listWorkflowRuns(resolveToken), getWorkflowRun: getWorkflowRun(resolveToken), listWorkflowJobs: listWorkflowJobs(resolveToken), + getWorkflowJobLogs: getWorkflowJobLogs(resolveToken), triggerWorkflow: triggerWorkflow(resolveToken, approval('triggerWorkflow')), cancelWorkflowRun: cancelWorkflowRun(resolveToken, approval('cancelWorkflowRun')), rerunWorkflowRun: rerunWorkflowRun(resolveToken, approval('rerunWorkflowRun')), @@ -235,8 +240,8 @@ export type GithubTools = AllGithubTools & ToolSet // Re-export individual tool factories for cherry-picking export { createOctokit } from './client' -export { getRepository, listBranches, getFileContent, getRepositoryTree, createBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository' -export { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, updatePullRequest, addPullRequestComment, updatePullRequestComment, deletePullRequestComment, listPullRequestFiles, listPullRequestReviews, createPullRequestReview, requestReviewers } from './tools/pull-requests' +export { getRepository, listBranches, getFileContent, getRepositoryTree, createBranch, deleteBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository' +export { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, updatePullRequest, addPullRequestComment, updatePullRequestComment, deletePullRequestComment, listPullRequestFiles, listPullRequestReviews, listPullRequestReviewThreads, createPullRequestReview, replyToReviewComment, resolveReviewThread, requestReviewers } from './tools/pull-requests' export { listIssues, getIssue, listIssueComments, createIssue, addIssueComment, updateIssueComment, deleteIssueComment, closeIssue, updateIssue, listLabels, addLabels, removeLabel, createLabel, updateLabel, deleteLabel, addAssignees, removeAssignees } from './tools/issues' export { listIssueReactions, addIssueReaction, listCommentReactions, addCommentReaction } from './tools/reactions' export { listDiscussions, getDiscussion, addDiscussionComment } from './tools/discussions' @@ -244,7 +249,7 @@ export { listNotifications, markNotificationRead } from './tools/notifications' export { searchCode, searchRepositories, searchIssues } from './tools/search' export { listCommits, getCommit, getBlame, compareCommits } from './tools/commits' export { listGists, getGist, listGistComments, createGist, updateGist, deleteGist, createGistComment } from './tools/gists' -export { listWorkflows, listWorkflowRuns, getWorkflowRun, listWorkflowJobs, triggerWorkflow, cancelWorkflowRun, rerunWorkflowRun } from './tools/workflows' +export { listWorkflows, listWorkflowRuns, getWorkflowRun, listWorkflowJobs, getWorkflowJobLogs, triggerWorkflow, cancelWorkflowRun, rerunWorkflowRun } from './tools/workflows' export { listCheckRuns, getCombinedStatus } from './tools/checks' export { listReleases, getLatestRelease, getRelease, createRelease, updateRelease, deleteRelease } from './tools/releases' export { getPullRequestContext, getIssueContext, getReleaseContext, getCiFailureContext } from './tools/bundles' diff --git a/packages/github-tools/src/tools/pull-requests.ts b/packages/github-tools/src/tools/pull-requests.ts index fd60827..eff2625 100644 --- a/packages/github-tools/src/tools/pull-requests.ts +++ b/packages/github-tools/src/tools/pull-requests.ts @@ -33,6 +33,15 @@ import { createPullRequestReviewInputSchema, createPullRequestReviewDescription, createPullRequestReviewCore, + listPullRequestReviewThreadsInputSchema, + listPullRequestReviewThreadsDescription, + listPullRequestReviewThreadsCore, + replyToReviewCommentInputSchema, + replyToReviewCommentDescription, + replyToReviewCommentCore, + resolveReviewThreadInputSchema, + resolveReviewThreadDescription, + resolveReviewThreadCore, requestReviewersInputSchema, requestReviewersDescription, requestReviewersCore, @@ -196,6 +205,47 @@ export const createPullRequestReview = (token: GithubTokenInput, { needsApproval execute: async args => createPullRequestReviewStep({ token: await resolveGithubToken(token), ...args }), }) +async function listPullRequestReviewThreadsStep(args: Parameters[0]) { + "use step" + return listPullRequestReviewThreadsCore(args) +} + +/** List review threads on a pull request with comments, resolution state, and reply/resolve IDs. */ +export const listPullRequestReviewThreads = (token: GithubTokenInput): GithubTool => + tool({ + description: listPullRequestReviewThreadsDescription, + inputSchema: listPullRequestReviewThreadsInputSchema, + execute: async args => listPullRequestReviewThreadsStep({ token: await resolveGithubToken(token), ...args }), + }) + +async function replyToReviewCommentStep(args: Parameters[0]) { + "use step" + return replyToReviewCommentCore(args) +} + +/** Reply to a pull request review comment in its review thread. Requires approval by default. */ +export const replyToReviewComment = (token: GithubTokenInput, { needsApproval = true }: ToolOptions = {}): GithubTool => + tool({ + description: replyToReviewCommentDescription, + needsApproval, + inputSchema: replyToReviewCommentInputSchema, + execute: async args => replyToReviewCommentStep({ token: await resolveGithubToken(token), ...args }), + }) + +async function resolveReviewThreadStep(args: Parameters[0]) { + "use step" + return resolveReviewThreadCore(args) +} + +/** Mark a pull request review thread as resolved. Requires approval by default. */ +export const resolveReviewThread = (token: GithubTokenInput, { needsApproval = true }: ToolOptions = {}): GithubTool => + tool({ + description: resolveReviewThreadDescription, + needsApproval, + inputSchema: resolveReviewThreadInputSchema, + execute: async args => resolveReviewThreadStep({ token: await resolveGithubToken(token), ...args }), + }) + async function requestReviewersStep(args: Parameters[0]) { "use step" return requestReviewersCore(args) diff --git a/packages/github-tools/src/tools/repository.ts b/packages/github-tools/src/tools/repository.ts index 40d34c6..3c874e6 100644 --- a/packages/github-tools/src/tools/repository.ts +++ b/packages/github-tools/src/tools/repository.ts @@ -15,6 +15,9 @@ import { createBranchInputSchema, createBranchDescription, createBranchCore, + deleteBranchInputSchema, + deleteBranchDescription, + deleteBranchCore, forkRepositoryInputSchema, forkRepositoryDescription, forkRepositoryCore, @@ -99,6 +102,20 @@ export const createBranch = (token: GithubTokenInput, { needsApproval = true }: execute: async args => createBranchStep({ token: await resolveGithubToken(token), ...args }), }) +async function deleteBranchStep(args: Parameters[0]) { + "use step" + return deleteBranchCore(args) +} + +/** Delete a branch from a GitHub repository permanently. Requires approval by default. */ +export const deleteBranch = (token: GithubTokenInput, { needsApproval = true }: ToolOptions = {}): GithubTool => + tool({ + description: deleteBranchDescription, + needsApproval, + inputSchema: deleteBranchInputSchema, + execute: async args => deleteBranchStep({ token: await resolveGithubToken(token), ...args }), + }) + async function forkRepositoryStep(args: Parameters[0]) { "use step" return forkRepositoryCore(args) diff --git a/packages/github-tools/src/tools/workflows.ts b/packages/github-tools/src/tools/workflows.ts index 68689da..027bc6d 100644 --- a/packages/github-tools/src/tools/workflows.ts +++ b/packages/github-tools/src/tools/workflows.ts @@ -12,6 +12,9 @@ import { listWorkflowJobsInputSchema, listWorkflowJobsDescription, listWorkflowJobsCore, + getWorkflowJobLogsInputSchema, + getWorkflowJobLogsDescription, + getWorkflowJobLogsCore, triggerWorkflowInputSchema, triggerWorkflowDescription, triggerWorkflowCore, @@ -77,6 +80,19 @@ export const listWorkflowJobs = (token: GithubTokenInput): GithubTool => execute: async args => listWorkflowJobsStep({ token: await resolveGithubToken(token), ...args }), }) +async function getWorkflowJobLogsStep(args: Parameters[0]) { + "use step" + return getWorkflowJobLogsCore(args) +} + +/** Get the log output of a workflow job, tail-truncated with timestamps stripped. */ +export const getWorkflowJobLogs = (token: GithubTokenInput): GithubTool => + tool({ + description: getWorkflowJobLogsDescription, + inputSchema: getWorkflowJobLogsInputSchema, + execute: async args => getWorkflowJobLogsStep({ token: await resolveGithubToken(token), ...args }), + }) + async function triggerWorkflowStep(args: Parameters[0]) { "use step" return triggerWorkflowCore(args)