Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions ts/packages/agents/github-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ The agent runs a `gh auth status` readiness probe at startup and pre-flights it

## Supported Actions

| Category | Actions |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| **Auth** | Login, logout, check status |
| **Issues** | Create, close, reopen, list, view, browse |
| **Pull Requests** | Create (including draft), close, merge, list, view, checkout, browse |
| **Repos** | Create, clone, delete, view (with field-specific queries like stars/forks), fork, star/unstar, browse |
| **Search** | Search repositories by keyword |
| **Status** | Dashboard summary of notifications, PRs, and issues |
| **Contributors** | Top N contributors for a repo |
| **Dependabot** | List alerts with severity/state filters |
| **Workflows** | View workflow runs and workflow details |
| **Other** | Codespaces, gists, releases, projects, labels, secrets, SSH keys, config, orgs |
| Category | Actions |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **Auth** | Login, logout, check status |
| **Issues** | Create, close, reopen, list, view, browse |
| **Pull Requests** | Create (including draft), close, merge, list, view, checkout, browse |
| **PR diagnostics** | List a PR's changed files (optionally with diff excerpts); explain failing checks with GitHub's error annotations |
| **Repos** | Create, clone, delete, view (with field-specific queries like stars/forks), fork, star/unstar, browse |
| **Search** | Search repositories by keyword |
| **Status** | Dashboard summary of notifications, PRs, and issues |
| **Contributors** | Top N contributors for a repo |
| **Dependabot** | List alerts with severity/state filters |
| **Workflows** | View workflow runs and workflow details |
| **Other** | Codespaces, gists, releases, projects, labels, secrets, SSH keys, config, orgs |

## Example Phrases

Expand All @@ -33,6 +34,10 @@ how many stars does microsoft/TypeAgent have
show top 10 contributors for microsoft/TypeAgent
create issue "Fix login bug" in microsoft/TypeAgent
close issue 42 in microsoft/TypeAgent
show files changed in PR 2196
show the diff for PR 2196
why is CI failing on PR 2196
why is https://github.com/cli/cli/pull/9000 failing
open a draft PR for my-feature branch
show newest 5 dependabot alerts in microsoft/TypeAgent
fork microsoft/TypeAgent
Expand All @@ -46,6 +51,8 @@ star microsoft/TypeAgent
- Status output uses **bold section headers** for readability
- Dependabot alerts are color-coded by severity (🔴 critical, 🟠 high, 🟡 medium, 🟢 low)
- Mutation actions (create, close, star, fork) return friendly emoji confirmation messages
- `prFiles` and `prFailedChecks` return typed structured data (`rawData`) alongside their display, and state explicitly when output was truncated, so an external MCP client can act on the result directly
- `prFiles` and `prFailedChecks` accept a pull request's web link in place of an `OWNER/REPO` slug, so a PR in another repository (or on a GitHub Enterprise host) can be diagnosed without leaving the current checkout

## Demo

Expand Down
9 changes: 9 additions & 0 deletions ts/packages/agents/github-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@typeagent/agent-sdk": "workspace:*"
},
"devDependencies": {
"@typeagent/action-grammar": "workspace:*",
"@typeagent/action-grammar-compiler": "workspace:*",
"@typeagent/action-schema-compiler": "workspace:*",
"@types/jest": "^29.5.7",
Expand Down Expand Up @@ -73,6 +74,14 @@
"dist/github-cliSchema.pas.json"
]
}
},
"tsc": {
"dependsOn": [
"@typeagent/action-grammar#tsc"
],
"after": [
"^*"
]
}
}
},
Expand Down
54 changes: 49 additions & 5 deletions ts/packages/agents/github-cli/src/github-cliActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
runSetupCommand,
whichExists,
} from "./setup.js";
import { buildTableBlock } from "./structuredResults.js";
import { GhResult, runPrFailedChecks, runPrFiles } from "./prDiagnostics.js";

const execFileAsync = promisify(execFile);

Expand Down Expand Up @@ -362,6 +364,42 @@ async function runGh(args: string[], timeoutMs = 30_000): Promise<string> {
return stdout.trim();
}

// Run a gh CLI command and return its full result, including a non-zero exit
// code, instead of throwing. Some gh commands report status through the exit
// code while still writing the JSON we asked for (`gh pr checks` exits
// non-zero when checks fail), and some failures should degrade a section of a
// result rather than fail the whole action. `exitCode` is -1 when gh never ran
// at all — a missing binary or a timeout.
//
// The buffer is larger than `runGh`'s because GitHub returns patches before we
// can truncate them. Eight MiB accommodates large PR responses while still
// bounding memory if a generated file contains an unusually large patch.
async function runGhCapture(
args: string[],
timeoutMs = 60_000,
): Promise<GhResult> {
try {
const { stdout, stderr } = await execFileAsync("gh", args, {
timeout: timeoutMs,
maxBuffer: 8 * 1024 * 1024,
Comment thread
GeorgeNgMsft marked this conversation as resolved.
windowsHide: true,
});
return { stdout, stderr, exitCode: 0 };
} catch (e) {
const err = e as {
stdout?: string;
stderr?: string;
code?: unknown;
message?: string;
};
return {
stdout: err.stdout ?? "",
stderr: err.stderr || err.message || "gh failed to run",
exitCode: typeof err.code === "number" ? err.code : -1,
};
}
}

// Sentinel values that mean "no assignee". `gh issue list --assignee <x>`
// treats <x> as a literal GitHub login, so "--assignee none" fails with
// "Could not find an assignee with the login 'none'". The supported way to
Expand Down Expand Up @@ -1258,13 +1296,9 @@ function makeStructuredTable<T>(
pageSize?: number;
},
): ActionResultSuccess {
const columns = colSpecs.map(({ value: _v, ...col }) => col);
const rows: TableCell[][] = objects.map((obj) =>
colSpecs.map((col) => col.value(obj)),
);
// Cap long lists to a first page (client reveals the rest via "Show
// more") unless the caller overrode it. All rows still ship.
const table: TableBlock = createTable(columns, rows, {
const table: TableBlock = buildTableBlock(colSpecs, objects, {
pageSize: 15,
...tableOptions,
});
Expand Down Expand Up @@ -1923,6 +1957,16 @@ async function executeAction(
}
action = validated.action;

// Multi-call read-only diagnostics. These compose several gh invocations
// into one structured result, so they run ahead of the single-command
// buildArgs/runGh path below.
if (action.actionName === "prFiles") {
return runPrFiles(action.parameters, runGhCapture);
}
if (action.actionName === "prFailedChecks") {
return runPrFailedChecks(action.parameters, runGhCapture);
}

const args = buildArgs(action);
if (!args) {
return createActionResultFromTextDisplay(
Expand Down
61 changes: 61 additions & 0 deletions ts/packages/agents/github-cli/src/github-cliSchema.agr
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,65 @@
}
};

<PrFiles> = show files changed in PR $(number:number) -> {
actionName: "prFiles",
parameters: {
number
}
}
| what files does PR $(number:number) (change | touch | edit | modify) -> {
actionName: "prFiles",
parameters: {
number
}
}
| show what's changed in PR $(number:number) -> {
actionName: "prFiles",
parameters: {
number
}
}
| show files changed in PR $(number:number) in $(repo:wildcard) -> {
Comment thread
GeorgeNgMsft marked this conversation as resolved.
actionName: "prFiles",
parameters: {
number,
repo
}
}
| show the diff for PR $(number:number) -> {
actionName: "prFiles",
parameters: {
number,
includePatch: true
}
};

<PrFailedChecks> = show failing checks for PR $(number:number) -> {
actionName: "prFailedChecks",
parameters: {
number
}
}
| why is (the)? CI failing on PR $(number:number) -> {
actionName: "prFailedChecks",
parameters: {
number
}
}
| why is (the)? pipeline failing on PR $(number:number) -> {
actionName: "prFailedChecks",
parameters: {
number
}
}
| show failing checks for PR $(number:number) in $(repo:wildcard) -> {
actionName: "prFailedChecks",
parameters: {
number,
repo
}
};

<RepoCreate> = create a new repository named $(name:wildcard) -> {
actionName: "repoCreate",
parameters: {
Expand Down Expand Up @@ -498,6 +557,8 @@ import { GithubCliActions } from "./github-cliSchema.ts";
| <PrView>
| <PrCheckout>
| <PrChecks>
| <PrFiles>
| <PrFailedChecks>
| <RepoCreate>
| <RepoClone>
| <RepoView>
Expand Down
74 changes: 74 additions & 0 deletions ts/packages/agents/github-cli/src/github-cliSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type GithubCliActions =
| PrViewAction
| PrCheckoutAction
| PrChecksAction
| PrFilesAction
| PrFailedChecksAction
| ProjectCreateAction
| ProjectDeleteAction
| ProjectListAction
Expand Down Expand Up @@ -391,6 +393,78 @@ export type PrChecksAction = {
};
};

// List the files a pull request changes, with per-file status and line counts,
// and optionally an excerpt of each file's diff. Use this to find out what a
// pull request actually touches. Read-only.
//
// Example:
// User: what files does PR 2196 change?
// Agent: { actionName: "prFiles", parameters: { number: 2196 } }
//
// Example:
// User: show me the diff for pull request 42 in microsoft/TypeAgent
// Agent: { actionName: "prFiles", parameters: { number: 42, repo: "microsoft/TypeAgent", includePatch: true } }
//
// Example:
// User: what does https://github.com/microsoft/TypeAgent/pull/42 change?
// Agent: { actionName: "prFiles", parameters: { number: 42, repo: "microsoft/TypeAgent" } }
export type PrFilesAction = {
actionName: "prFiles";
parameters: {
// The pull request number.
number: number;

// OWNER/REPO slug (e.g. "microsoft/TypeAgent"), or the pull request's
// web link. Omit to use the repository in the current directory.
repo?: string;

// Include an excerpt of each file's diff. Off by default because
// patches are large; turn it on to see the actual code changes.
includePatch?: boolean;

// How many files to return, newest API order. Defaults to 50 to keep
// the structured display manageable; callers can request up to 300.
maxFiles?: number;

// How many lines of each file's patch to keep. 1-200, default 40.
// Only meaningful with includePatch.
maxPatchLines?: number;
};
};

// Explain why a pull request's checks are red: which checks failed, when, and
// the specific error annotations GitHub recorded for each one. Use this to
// diagnose CI failures. Read-only.
//
// Example:
// User: why is CI failing on PR 2196?
// Agent: { actionName: "prFailedChecks", parameters: { number: 2196 } }
//
// Example:
// User: show the failing checks for pull request 42 in microsoft/TypeAgent
// Agent: { actionName: "prFailedChecks", parameters: { number: 42, repo: "microsoft/TypeAgent" } }
//
// Example:
// User: why is https://github.com/microsoft/TypeAgent/pull/42 red?
// Agent: { actionName: "prFailedChecks", parameters: { number: 42, repo: "microsoft/TypeAgent" } }
export type PrFailedChecksAction = {
actionName: "prFailedChecks";
parameters: {
// The pull request number.
number: number;

// OWNER/REPO slug (e.g. "microsoft/TypeAgent"), or the pull request's
// web link. Omit to use the repository in the current directory.
repo?: string;

// How many failing checks to describe in detail. 1-20, default 5.
maxChecks?: number;

// How many annotations to return per failing check. 1-50, default 10.
maxAnnotations?: number;
};
};

export type ProjectCreateAction = {
actionName: "projectCreate";
parameters: {
Expand Down
Loading
Loading