@trim21/personal-pi-extensions 0.1.683 → 0.1.685

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,64 +1,79 @@
1
+ /**
2
+ * `list-github-prs`:与 `list-github-issues` 同一套两条分支(关键词搜索走 octokit,
3
+ * 浏览走 `gh pr list --json`),归一成同一套 `SearchHit`。
4
+ */
5
+
1
6
  import { Type } from "typebox";
2
7
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
8
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
9
  import {
10
+ browseList,
5
11
  type GhClient,
6
12
  type ListFilters,
7
- listGithub,
8
- searchList,
13
+ renderHitList,
14
+ type StructuredResultOf,
9
15
  subtitlePendant,
10
16
  type ToolCall,
11
- type ToolResult,
12
17
  toToolResult,
18
+ withStructuredResult,
13
19
  } from "../base.js";
20
+ import { ghHitListPayloadSchema } from "../schemas.js";
14
21
 
15
- async function listPrs(gh: GhClient, call: ToolCall<ListFilters>): Promise<ToolResult> {
22
+ async function listPrs(
23
+ gh: GhClient,
24
+ call: ToolCall<ListFilters>,
25
+ ): Promise<StructuredResultOf<typeof ghHitListPayloadSchema>> {
16
26
  const { params, ctx, signal } = call;
17
- const result = toToolResult(
18
- params.keywords
19
- ? await searchList("pr", params, gh.search)
20
- : await listGithub("pr", params, { cwd: ctx.cwd, signal, input: params }),
21
- params,
22
- );
27
+ const { repo, hits } = params.keywords
28
+ ? { repo: params.repo, hits: await gh.search.search("pr", params) }
29
+ : await browseList("pr", params, { cwd: ctx.cwd, signal, input: params });
30
+ const text = renderHitList("pr", hits, { repo, fields: params.fields });
31
+ const result = toToolResult(text, params);
23
32
  result.details.pendant = subtitlePendant(params);
24
- return result;
33
+ // 文本可能被行数/字节预算截断,载荷给全部命中
34
+ return withStructuredResult(result, { text, items: hits });
25
35
  }
26
36
 
27
37
  export function addListPrsTool(gh: GhClient, bus: ToolBus) {
28
- bus.register({
29
- name: "list-github-prs",
30
- label: "GitHub PRs List",
31
- description:
32
- 'List GitHub pull requests with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open PRs — pass state="merged", state="closed" (merged excluded) or state="all" to broaden. Set fields to choose the columns of each result row.',
33
- promptSnippet: "List or search GitHub PRs",
34
- parameters: Type.Object({
35
- repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
36
- keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
37
- state: Type.Optional(
38
- Type.String({
39
- description:
40
- "open, closed, merged, all (default: open; all applies to keyword search and covers open + closed + merged)",
41
- }),
42
- ),
43
- label: Type.Optional(Type.String({ description: "Filter by label" })),
44
- // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
45
- // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
46
- // 两条传输都支持,因此本仓库不要再实现一次展开。
47
- author: Type.Optional(Type.String({ description: "Filter by author ('@me' for yourself)" })),
48
- assignee: Type.Optional(
49
- Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
50
- ),
51
- milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
52
- limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
53
- fields: Type.Optional(
54
- Type.String({
55
- description:
56
- "Comma-separated columns for keyword-search rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt,mergedAt",
57
- }),
58
- ),
38
+ bus.register(
39
+ defineStructuredTool({
40
+ name: "list-github-prs",
41
+ label: "GitHub PRs List",
42
+ description:
43
+ 'List GitHub pull requests with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open PRs — pass state="merged", state="closed" (merged excluded) or state="all" to broaden. Set fields to choose the columns of each result row.',
44
+ promptSnippet: "List or search GitHub PRs",
45
+ parameters: Type.Object({
46
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
47
+ keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
48
+ state: Type.Optional(
49
+ Type.String({
50
+ description:
51
+ "open, closed, merged, all (default: open; all applies to keyword search and covers open + closed + merged)",
52
+ }),
53
+ ),
54
+ label: Type.Optional(Type.String({ description: "Filter by label" })),
55
+ // `@me` 由 GitHub 的搜索 / 过滤 API 解析:不带 keywords 时 gh 自行展开(GraphQL 过滤分支)
56
+ // 或按字面量转发(搜索分支),带 keywords 时我们直接把 `assignee:@me` 交给搜索 API。
57
+ // 两条传输都支持,因此本仓库不要再实现一次展开。
58
+ author: Type.Optional(
59
+ Type.String({ description: "Filter by author ('@me' for yourself)" }),
60
+ ),
61
+ assignee: Type.Optional(
62
+ Type.String({ description: "Filter by assignee ('@me' for yourself)" }),
63
+ ),
64
+ milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
65
+ limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
66
+ fields: Type.Optional(
67
+ Type.String({
68
+ description:
69
+ "Comma-separated columns for the rows (default: number,state,title,labels,updatedAt; adds repo when no repo given). Valid: number,state,title,url,author,labels,milestone,assignees,comments,repo,createdAt,updatedAt,closedAt,mergedAt",
70
+ }),
71
+ ),
72
+ }),
73
+ structuredSchema: ghHitListPayloadSchema,
74
+ async execute(_id, params, signal, onUpdate, ctx) {
75
+ return listPrs(gh, { params, ctx, signal, onUpdate });
76
+ },
59
77
  }),
60
- async execute(_id, params, signal, onUpdate, ctx) {
61
- return listPrs(gh, { params, ctx, signal, onUpdate });
62
- },
63
- });
78
+ );
64
79
  }
@@ -1,30 +1,47 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderReleaseList } from "../render.js";
14
+ import { ghReleaseListPayloadSchema, ghReleaseSummarySchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的 release 字段,与渲染和载荷一一对应。 */
17
+ const RELEASE_FIELDS = "tagName,name,isLatest,isPrerelease,isDraft,publishedAt,createdAt";
5
18
 
6
19
  export function addListReleasesTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "list-github-releases",
9
- label: "GitHub Releases List",
10
- description: "List GitHub releases.",
11
- promptSnippet: "List GitHub releases",
12
- parameters: Type.Object({
13
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
14
- limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
20
+ bus.register(
21
+ defineStructuredTool({
22
+ name: "list-github-releases",
23
+ label: "GitHub Releases List",
24
+ description:
25
+ "List GitHub releases. Each row is: tag, flags (latest/prerelease/draft), published date, name.",
26
+ promptSnippet: "List GitHub releases",
27
+ parameters: Type.Object({
28
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
29
+ limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
30
+ }),
31
+ structuredSchema: ghReleaseListPayloadSchema,
32
+ async execute(_id, params, signal, _onUpdate, ctx) {
33
+ const { repo, limit } = params;
34
+ const args = ["release", "list", ...repoArgs(repo), "--json", RELEASE_FIELDS];
35
+ if (limit) {
36
+ args.push("--limit", String(limit));
37
+ }
38
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
39
+ const releases = parseWithSchema(Type.Array(ghReleaseSummarySchema), JSON.parse(stdout));
40
+ const text = renderReleaseList(releases);
41
+ const result = toToolResult(text, params);
42
+ result.details.pendant = subtitlePendant(params);
43
+ return withStructuredResult(result, { text, releases });
44
+ },
15
45
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { repo, limit } = params;
18
- const args = ["release", "list", ...repoArgs(repo)];
19
- if (limit) {
20
- args.push("--limit", String(limit));
21
- }
22
- const result = toToolResult(
23
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
24
- params,
25
- );
26
- result.details.pendant = subtitlePendant(params);
27
- return result;
28
- },
29
- });
46
+ );
30
47
  }
@@ -1,40 +1,58 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderRunList } from "../render.js";
14
+ import { ghRunListPayloadSchema, ghRunSummarySchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的运行字段,与渲染和载荷一一对应。 */
17
+ const RUN_FIELDS =
18
+ "databaseId,displayTitle,status,conclusion,workflowName,headBranch,event,createdAt,url";
5
19
 
6
20
  export function addListWorkflowRunsTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "list-github-workflow-runs",
9
- label: "GitHub Workflow Runs",
10
- description: "List GitHub Actions workflow runs.",
11
- promptSnippet: "List GitHub workflow runs",
12
- parameters: Type.Object({
13
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
14
- limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
15
- status: Type.Optional(
16
- Type.String({ description: "Filter by status: success, failure, cancelled, etc." }),
17
- ),
18
- workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
21
+ bus.register(
22
+ defineStructuredTool({
23
+ name: "list-github-workflow-runs",
24
+ label: "GitHub Workflow Runs",
25
+ description:
26
+ "List GitHub Actions workflow runs. Each row is: id, status, conclusion, workflow, branch, event, created date, url.",
27
+ promptSnippet: "List GitHub workflow runs",
28
+ parameters: Type.Object({
29
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
30
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
31
+ status: Type.Optional(
32
+ Type.String({ description: "Filter by status: success, failure, cancelled, etc." }),
33
+ ),
34
+ workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
35
+ }),
36
+ structuredSchema: ghRunListPayloadSchema,
37
+ async execute(_id, params, signal, _onUpdate, ctx) {
38
+ const { repo, limit, status, workflow } = params;
39
+ const args = ["run", "list", ...repoArgs(repo), "--json", RUN_FIELDS];
40
+ if (limit) {
41
+ args.push("--limit", String(limit));
42
+ }
43
+ if (status) {
44
+ args.push("--status", status);
45
+ }
46
+ if (workflow) {
47
+ args.push("--workflow", workflow);
48
+ }
49
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
50
+ const runs = parseWithSchema(Type.Array(ghRunSummarySchema), JSON.parse(stdout));
51
+ const text = renderRunList(runs);
52
+ const result = toToolResult(text, params);
53
+ result.details.pendant = subtitlePendant(params);
54
+ return withStructuredResult(result, { text, runs });
55
+ },
19
56
  }),
20
- async execute(_id, params, signal, _onUpdate, ctx) {
21
- const { repo, limit, status, workflow } = params;
22
- const args = ["run", "list", ...repoArgs(repo)];
23
- if (limit) {
24
- args.push("--limit", String(limit));
25
- }
26
- if (status) {
27
- args.push("--status", status);
28
- }
29
- if (workflow) {
30
- args.push("--workflow", workflow);
31
- }
32
- const result = toToolResult(
33
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
34
- params,
35
- );
36
- result.details.pendant = subtitlePendant(params);
37
- return result;
38
- },
39
- });
57
+ );
40
58
  }
@@ -1,27 +1,38 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
+ import {
5
+ type GhClient,
6
+ ghExec,
7
+ repoArgs,
8
+ subtitlePendant,
9
+ toToolResult,
10
+ withStructuredResult,
11
+ } from "../base.js";
12
+ import { parseDiffStats } from "../render.js";
13
+ import { ghDiffPayloadSchema } from "../schemas.js";
5
14
 
6
15
  export function addReadPrDiffTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "read-github-pr-diff",
9
- label: "GitHub PR Diff",
10
- description: "Get the diff of a GitHub pull request.",
11
- promptSnippet: "Read a GitHub PR diff",
12
- parameters: Type.Object({
13
- number: Type.Union([Type.Number(), Type.String()], { description: "PR number" }),
14
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
16
+ bus.register(
17
+ defineStructuredTool({
18
+ name: "read-github-pr-diff",
19
+ label: "GitHub PR Diff",
20
+ description: "Get the diff of a GitHub pull request.",
21
+ promptSnippet: "Read a GitHub PR diff",
22
+ parameters: Type.Object({
23
+ number: Type.Union([Type.Number(), Type.String()], { description: "PR number" }),
24
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
25
+ }),
26
+ structuredSchema: ghDiffPayloadSchema,
27
+ async execute(_id, params, signal, _onUpdate, ctx) {
28
+ const { number, repo } = params;
29
+ const args = ["pr", "diff", String(number), ...repoArgs(repo)];
30
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
31
+ const result = toToolResult(stdout, params);
32
+ result.details.pendant = subtitlePendant(params, "number");
33
+ // 文本仍是原始 diff(`gh pr diff` 没有 --json),载荷从同一份 diff 解析出变更统计
34
+ return withStructuredResult(result, { text: stdout, ...parseDiffStats(stdout) });
35
+ },
15
36
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { number, repo } = params;
18
- const args = ["pr", "diff", String(number), ...repoArgs(repo)];
19
- const result = toToolResult(
20
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
21
- params,
22
- );
23
- result.details.pendant = subtitlePendant(params, "number");
24
- return result;
25
- },
26
- });
37
+ );
27
38
  }
@@ -1,30 +1,58 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, repoArgs, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ repoArgs,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderReleaseView } from "../render.js";
14
+ import { ghReleaseViewPayloadSchema, ghReleaseViewSchema } from "../schemas.js";
15
+
16
+ /** 我们向 gh 要的 release 字段,与渲染和载荷一一对应。 */
17
+ const RELEASE_FIELDS = [
18
+ "tagName",
19
+ "name",
20
+ "body",
21
+ "url",
22
+ "isDraft",
23
+ "isPrerelease",
24
+ "createdAt",
25
+ "publishedAt",
26
+ "targetCommitish",
27
+ "author",
28
+ "assets",
29
+ ].join(",");
5
30
 
6
31
  export function addReadReleaseTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "read-github-release",
9
- label: "GitHub Release",
10
- description: "Get details of a specific GitHub release by tag.",
11
- promptSnippet: "Read a GitHub release",
12
- parameters: Type.Object({
13
- tag: Type.String({ description: "Release tag name" }),
14
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
32
+ bus.register(
33
+ defineStructuredTool({
34
+ name: "read-github-release",
35
+ label: "GitHub Release",
36
+ description:
37
+ "Get details of a specific GitHub release by tag: metadata, asset list and release notes.",
38
+ promptSnippet: "Read a GitHub release",
39
+ parameters: Type.Object({
40
+ tag: Type.String({ description: "Release tag name" }),
41
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
42
+ }),
43
+ structuredSchema: ghReleaseViewPayloadSchema,
44
+ async execute(_id, params, signal, _onUpdate, ctx) {
45
+ const { tag, repo } = params;
46
+ const stdout = await ghExec(
47
+ ["release", "view", tag, ...repoArgs(repo), "--json", RELEASE_FIELDS],
48
+ { cwd: ctx.cwd, signal, input: params },
49
+ );
50
+ const view = parseWithSchema(ghReleaseViewSchema, JSON.parse(stdout));
51
+ const text = renderReleaseView(view);
52
+ const result = toToolResult(text, params);
53
+ result.details.pendant = subtitlePendant(params, "tag");
54
+ return withStructuredResult(result, { text, release: view });
55
+ },
15
56
  }),
16
- async execute(_id, params, signal, _onUpdate, ctx) {
17
- const { tag, repo } = params;
18
- const result = toToolResult(
19
- await ghExec(["release", "view", tag, ...repoArgs(repo)], {
20
- cwd: ctx.cwd,
21
- signal,
22
- input: params,
23
- }),
24
- params,
25
- );
26
- result.details.pendant = subtitlePendant(params, "tag");
27
- return result;
28
- },
29
- });
57
+ );
30
58
  }
@@ -1,29 +1,65 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import type { ToolBus } from "../../lib/tool-bus.js";
4
- import { type GhClient, ghExec, subtitlePendant, toToolResult } from "../base.js";
3
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
+ import {
6
+ type GhClient,
7
+ ghExec,
8
+ subtitlePendant,
9
+ toToolResult,
10
+ withStructuredResult,
11
+ } from "../base.js";
12
+ import { renderRepoView } from "../render.js";
13
+ import { ghRepoPayloadSchema, ghRepoViewSchema } from "../schemas.js";
14
+
15
+ /** 我们向 gh 要的仓库字段,与渲染和载荷一一对应。 */
16
+ const REPO_FIELDS = [
17
+ "name",
18
+ "nameWithOwner",
19
+ "description",
20
+ "url",
21
+ "homepageUrl",
22
+ "visibility",
23
+ "isPrivate",
24
+ "isFork",
25
+ "isArchived",
26
+ "stargazerCount",
27
+ "forkCount",
28
+ "primaryLanguage",
29
+ "defaultBranchRef",
30
+ "licenseInfo",
31
+ "issues",
32
+ "pullRequests",
33
+ "pushedAt",
34
+ "createdAt",
35
+ "updatedAt",
36
+ ].join(",");
5
37
 
6
38
  export function addReadRepoTool(_gh: GhClient, bus: ToolBus) {
7
- bus.register({
8
- name: "read-github-repo",
9
- label: "GitHub Repo",
10
- description: "Get repository information.",
11
- promptSnippet: "Read GitHub repo info",
12
- parameters: Type.Object({
13
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
39
+ bus.register(
40
+ defineStructuredTool({
41
+ name: "read-github-repo",
42
+ label: "GitHub Repo",
43
+ description: "Get repository information.",
44
+ promptSnippet: "Read GitHub repo info",
45
+ parameters: Type.Object({
46
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
47
+ }),
48
+ structuredSchema: ghRepoPayloadSchema,
49
+ async execute(_id, params, signal, _onUpdate, ctx) {
50
+ const { repo } = params;
51
+ const args = ["repo", "view"];
52
+ if (repo) {
53
+ args.push(repo);
54
+ }
55
+ args.push("--json", REPO_FIELDS);
56
+ const stdout = await ghExec(args, { cwd: ctx.cwd, signal, input: params });
57
+ const view = parseWithSchema(ghRepoViewSchema, JSON.parse(stdout));
58
+ const text = renderRepoView(view);
59
+ const result = toToolResult(text, params);
60
+ result.details.pendant = subtitlePendant(params);
61
+ return withStructuredResult(result, { text, repo: view });
62
+ },
14
63
  }),
15
- async execute(_id, params, signal, _onUpdate, ctx) {
16
- const { repo } = params;
17
- const args = ["repo", "view"];
18
- if (repo) {
19
- args.push(repo);
20
- }
21
- const result = toToolResult(
22
- await ghExec(args, { cwd: ctx.cwd, signal, input: params }),
23
- params,
24
- );
25
- result.details.pendant = subtitlePendant(params);
26
- return result;
27
- },
28
- });
64
+ );
29
65
  }
@@ -1,46 +1,62 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import { runGh } from "../../lib/gh-process.js";
4
- import type { ToolBus } from "../../lib/tool-bus.js";
5
- import { type GhClient, repoArgs, subtitlePendant } from "../base.js";
4
+ import { parseWithSchema } from "../../lib/parse-with-schema.js";
5
+ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
6
+ import { type GhClient, ghExec, repoArgs, subtitlePendant } from "../base.js";
7
+ import { ghRunPayloadSchema, ghRunSummarySchema } from "../schemas.js";
8
+
9
+ /** 收尾时查运行状态要的字段(`gh run watch` 自己没有 `--json`)。 */
10
+ const RUN_FIELDS =
11
+ "databaseId,displayTitle,status,conclusion,workflowName,headBranch,event,createdAt,updatedAt,startedAt,url";
6
12
 
7
13
  export function addWatchRunTool(_gh: GhClient, bus: ToolBus) {
8
- bus.register({
9
- name: "watch-github-run",
10
- label: "Watch GitHub Workflow Run",
11
- description:
12
- "Watch a GitHub Actions workflow run until it completes. " +
13
- "Blocks until the run finishes and shows the final status.",
14
- promptSnippet: "Watch and wait for a GitHub Actions run to complete",
15
- parameters: Type.Object({
16
- run_id: Type.Union([Type.Number(), Type.String()], { description: "Workflow run ID" }),
17
- repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
14
+ bus.register(
15
+ defineStructuredTool({
16
+ name: "watch-github-run",
17
+ label: "Watch GitHub Workflow Run",
18
+ description:
19
+ "Watch a GitHub Actions workflow run until it completes. " +
20
+ "Blocks until the run finishes and shows the final status.",
21
+ promptSnippet: "Watch and wait for a GitHub Actions run to complete",
22
+ parameters: Type.Object({
23
+ run_id: Type.Union([Type.Number(), Type.String()], { description: "Workflow run ID" }),
24
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
25
+ }),
26
+ structuredSchema: ghRunPayloadSchema,
27
+ async execute(_id, params, signal, onUpdate, ctx) {
28
+ const { run_id, repo } = params;
29
+
30
+ const pendant = subtitlePendant(params, "run_id");
31
+ onUpdate?.({
32
+ content: [{ type: "text", text: `Watching workflow run ${run_id}...` }],
33
+ details: {},
34
+ });
35
+
36
+ const result = await runGh(["run", "watch", String(run_id), ...repoArgs(repo)], {
37
+ cwd: ctx.cwd,
38
+ signal,
39
+ timeout: 600_000,
40
+ });
41
+
42
+ if (result.code !== 0) {
43
+ throw new Error(`gh run watch failed: ${result.stderr || `exit code ${result.code}`}`);
44
+ }
45
+
46
+ const text = `## Workflow Run ${run_id} Completed\n\n${result.stdout}`;
47
+ // 监控的是状态变化,结论要另外查一次(watch 的文本里只有过程)
48
+ const stdout = await ghExec(
49
+ ["run", "view", String(run_id), ...repoArgs(repo), "--json", RUN_FIELDS],
50
+ { cwd: ctx.cwd, signal, input: params },
51
+ );
52
+ const run = parseWithSchema(ghRunSummarySchema, JSON.parse(stdout));
53
+
54
+ return {
55
+ content: [{ type: "text" as const, text }],
56
+ details: { exitCode: 0, input: params, ...(pendant && { pendant }) },
57
+ structuredResult: { ok: true as const, value: { text, run } },
58
+ };
59
+ },
18
60
  }),
19
- async execute(_id, params, signal, onUpdate, ctx) {
20
- const { run_id, repo } = params;
21
-
22
- const pendant = subtitlePendant(params, "run_id");
23
- onUpdate?.({
24
- content: [{ type: "text", text: `Watching workflow run ${run_id}...` }],
25
- details: {},
26
- });
27
-
28
- const result = await runGh(["run", "watch", String(run_id), ...repoArgs(repo)], {
29
- cwd: ctx.cwd,
30
- signal,
31
- timeout: 600_000,
32
- });
33
-
34
- if (result.code !== 0) {
35
- throw new Error(`gh run watch failed: ${result.stderr || `exit code ${result.code}`}`);
36
- }
37
-
38
- return {
39
- content: [
40
- { type: "text", text: `## Workflow Run ${run_id} Completed\n\n${result.stdout}` },
41
- ],
42
- details: { exitCode: 0, input: params, ...(pendant && { pendant }) },
43
- };
44
- },
45
- });
61
+ );
46
62
  }