@trim21/personal-pi-extensions 0.1.685 → 0.1.687

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.
@@ -4,8 +4,8 @@ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- ghExec,
8
- repoArgs,
7
+ resolveRepo,
8
+ splitRepo,
9
9
  subtitlePendant,
10
10
  toToolResult,
11
11
  withStructuredResult,
@@ -13,10 +13,7 @@ import {
13
13
  import { renderReleaseList } from "../render.js";
14
14
  import { ghReleaseListPayloadSchema, ghReleaseSummarySchema } from "../schemas.js";
15
15
 
16
- /** 我们向 gh 要的 release 字段,与渲染和载荷一一对应。 */
17
- const RELEASE_FIELDS = "tagName,name,isLatest,isPrerelease,isDraft,publishedAt,createdAt";
18
-
19
- export function addListReleasesTool(_gh: GhClient, bus: ToolBus) {
16
+ export function addListReleasesTool(gh: GhClient, bus: ToolBus) {
20
17
  bus.register(
21
18
  defineStructuredTool({
22
19
  name: "list-github-releases",
@@ -31,13 +28,17 @@ export function addListReleasesTool(_gh: GhClient, bus: ToolBus) {
31
28
  structuredSchema: ghReleaseListPayloadSchema,
32
29
  async execute(_id, params, signal, _onUpdate, ctx) {
33
30
  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);
31
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
32
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
33
+ const items = await gh.reads.listReleases(owner, repoName, limit, signal);
34
+ const releases = parseWithSchema(Type.Array(ghReleaseSummarySchema), items);
35
+ // REST 没有 isLatest:按列表顺序取第一个非 draft / prerelease 的条目
36
+ const latestIndex = releases.findIndex(
37
+ (release) => release.draft !== true && release.prerelease !== true,
38
+ );
39
+ const text = renderReleaseList(
40
+ releases.map((release, index) => ({ ...release, latest: index === latestIndex })),
41
+ );
41
42
  const result = toToolResult(text, params);
42
43
  result.details.pendant = subtitlePendant(params);
43
44
  return withStructuredResult(result, { text, releases });
@@ -4,8 +4,8 @@ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- ghExec,
8
- repoArgs,
7
+ resolveRepo,
8
+ splitRepo,
9
9
  subtitlePendant,
10
10
  toToolResult,
11
11
  withStructuredResult,
@@ -13,11 +13,7 @@ import {
13
13
  import { renderRunList } from "../render.js";
14
14
  import { ghRunListPayloadSchema, ghRunSummarySchema } from "../schemas.js";
15
15
 
16
- /** 我们向 gh 要的运行字段,与渲染和载荷一一对应。 */
17
- const RUN_FIELDS =
18
- "databaseId,displayTitle,status,conclusion,workflowName,headBranch,event,createdAt,url";
19
-
20
- export function addListWorkflowRunsTool(_gh: GhClient, bus: ToolBus) {
16
+ export function addListWorkflowRunsTool(gh: GhClient, bus: ToolBus) {
21
17
  bus.register(
22
18
  defineStructuredTool({
23
19
  name: "list-github-workflow-runs",
@@ -36,18 +32,10 @@ export function addListWorkflowRunsTool(_gh: GhClient, bus: ToolBus) {
36
32
  structuredSchema: ghRunListPayloadSchema,
37
33
  async execute(_id, params, signal, _onUpdate, ctx) {
38
34
  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));
35
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
36
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
37
+ const items = await gh.reads.listRuns(owner, repoName, { workflow, status, limit }, signal);
38
+ const runs = parseWithSchema(Type.Array(ghRunSummarySchema), items);
51
39
  const text = renderRunList(runs);
52
40
  const result = toToolResult(text, params);
53
41
  result.details.pendant = subtitlePendant(params);
@@ -14,7 +14,6 @@ import { createSeqState } from "../../lib/seq-state.js";
14
14
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
15
15
  import {
16
16
  type GhClient,
17
- ghExec,
18
17
  resolveRepo,
19
18
  splitRepo,
20
19
  structuredFailure,
@@ -55,10 +54,9 @@ export function jobLogPath(repo: string, runId: string, jobId: number): string {
55
54
  }
56
55
 
57
56
  async function getJobLog(
57
+ gh: GhClient,
58
58
  job: RunJob,
59
59
  signal: AbortSignal | undefined,
60
- cwd: string | undefined,
61
- input?: unknown,
62
60
  ): Promise<string> {
63
61
  // The log download only accepts a job id, and the cache is keyed on the
64
62
  // canonical repo/run from the job itself, not on the caller's `repo` string.
@@ -76,21 +74,10 @@ async function getJobLog(
76
74
  // Not cached, fetch from GitHub
77
75
  }
78
76
 
79
- // `gh api` refuses to print responses that contain terminal escape
80
- // sequences unless `--allow-escape-sequences` is passed. Job logs carry
81
- // ANSI color codes, so without this flag the download always fails with
82
- // "the response contains terminal escape sequences; pass
83
- // --allow-escape-sequences to output it anyway". The raw bytes are kept
84
- // as-is (the file is the log exactly as GitHub delivers it); the tool never
85
- // echoes them, and the TUI strips ANSI when rendering tool results.
86
- const log = await ghExec(
87
- ["api", "--allow-escape-sequences", `/repos/${repo}/actions/jobs/${job.id}/logs`],
88
- {
89
- cwd,
90
- signal,
91
- input,
92
- },
93
- );
77
+ // 日志原样落盘(GitHub 返回什么就是什么,含 ANSI 转义与 runner 时间戳);工具从不回显
78
+ // 它,模型自己读文件,TUI 渲染工具结果时会自己剥 ANSI。
79
+ const { owner, repo: name } = splitRepo(repo);
80
+ const log = await gh.reads.jobLogs(owner, name, job.id, signal);
94
81
 
95
82
  // Write to cache
96
83
  await mkdir(cacheDir, { recursive: true });
@@ -420,7 +407,7 @@ async function ciLogs(gh: GhClient, call: ToolCall<JobIdParams>): Promise<CiLogs
420
407
  details: {},
421
408
  });
422
409
 
423
- const rawLog = await getJobLog(target, signal, ctx.cwd, params);
410
+ const rawLog = await getJobLog(gh, target, signal);
424
411
  const index = jobLogIndex(target, rawLog);
425
412
 
426
413
  return withStructuredResult(
@@ -3,14 +3,14 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- ghExec,
7
- repoArgs,
6
+ resolveRepo,
7
+ splitRepo,
8
8
  subtitlePendant,
9
9
  toStructuredJsonResult,
10
10
  } from "../base.js";
11
11
  import { issueCommentsSchema } from "../schemas.js";
12
12
 
13
- export function addReadIssueCommentsTool(_gh: GhClient, bus: ToolBus) {
13
+ export function addReadIssueCommentsTool(gh: GhClient, bus: ToolBus) {
14
14
  bus.register(
15
15
  defineStructuredTool({
16
16
  name: "read-github-issue-comments",
@@ -24,11 +24,14 @@ export function addReadIssueCommentsTool(_gh: GhClient, bus: ToolBus) {
24
24
  structuredSchema: issueCommentsSchema,
25
25
  async execute(_id, params, signal, _onUpdate, ctx) {
26
26
  const { number, repo } = params;
27
- const out = await ghExec(
28
- ["issue", "view", String(number), ...repoArgs(repo), "--json", "comments"],
29
- { cwd: ctx.cwd, signal, input: params },
27
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
28
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
29
+ const comments = await gh.reads.issueComments(owner, repoName, Number(number), signal);
30
+ const result = toStructuredJsonResult(
31
+ JSON.stringify({ comments }, null, 2),
32
+ params,
33
+ issueCommentsSchema,
30
34
  );
31
- const result = toStructuredJsonResult(out, params, issueCommentsSchema);
32
35
  result.details.pendant = subtitlePendant(params, "number");
33
36
  return result;
34
37
  },
@@ -3,14 +3,14 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- ghExec,
7
- repoArgs,
6
+ resolveRepo,
7
+ splitRepo,
8
8
  subtitlePendant,
9
9
  toStructuredJsonResult,
10
10
  } from "../base.js";
11
11
  import { issueViewSchema } from "../schemas.js";
12
12
 
13
- export function addReadIssueTool(_gh: GhClient, bus: ToolBus) {
13
+ export function addReadIssueTool(gh: GhClient, bus: ToolBus) {
14
14
  bus.register(
15
15
  defineStructuredTool({
16
16
  name: "read-github-issue",
@@ -24,18 +24,14 @@ export function addReadIssueTool(_gh: GhClient, bus: ToolBus) {
24
24
  structuredSchema: issueViewSchema,
25
25
  async execute(_id, params, signal, _onUpdate, ctx) {
26
26
  const { number, repo } = params;
27
- const out = await ghExec(
28
- [
29
- "issue",
30
- "view",
31
- String(number),
32
- ...repoArgs(repo),
33
- "--json",
34
- "title,state,body,author,createdAt,updatedAt,closedAt,url,labels,assignees,comments,milestone,number",
35
- ],
36
- { cwd: ctx.cwd, signal, input: params },
27
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
28
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
29
+ const data = await gh.reads.issue(owner, repoName, Number(number), signal);
30
+ const result = toStructuredJsonResult(
31
+ JSON.stringify(data, null, 2),
32
+ params,
33
+ issueViewSchema,
37
34
  );
38
- const result = toStructuredJsonResult(out, params, issueViewSchema);
39
35
  result.details.pendant = subtitlePendant(params, "number");
40
36
  return result;
41
37
  },
@@ -2,17 +2,15 @@ import { Type } from "typebox";
2
2
 
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
- ghApiList,
6
5
  type GhClient,
7
- ghExec,
8
- repoArgs,
9
6
  resolveRepo,
7
+ splitRepo,
10
8
  subtitlePendant,
11
9
  toStructuredJsonResult,
12
10
  } from "../base.js";
13
11
  import { prCommentsSchema } from "../schemas.js";
14
12
 
15
- export function addReadPrCommentsTool(_gh: GhClient, bus: ToolBus) {
13
+ export function addReadPrCommentsTool(gh: GhClient, bus: ToolBus) {
16
14
  bus.register(
17
15
  defineStructuredTool({
18
16
  name: "read-github-pr-comments",
@@ -33,42 +31,25 @@ export function addReadPrCommentsTool(_gh: GhClient, bus: ToolBus) {
33
31
  structuredSchema: prCommentsSchema,
34
32
  async execute(_id, params, signal, _onUpdate, ctx) {
35
33
  const { number, repo, reviews } = params;
36
- let out: string;
37
- if (reviews) {
38
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
34
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
35
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
36
+ const pullNumber = Number(number);
39
37
 
40
- const [reviewComments, reviewSummaries] = await Promise.all([
41
- ghApiList(`/repos/${effectiveRepo}/pulls/${String(number)}/comments`, {
42
- cwd: ctx.cwd,
43
- signal,
44
- input: params,
45
- }),
46
- ghApiList(`/repos/${effectiveRepo}/pulls/${String(number)}/reviews`, {
47
- cwd: ctx.cwd,
48
- signal,
49
- input: params,
50
- }),
51
- ]);
38
+ // reviews=true 给行内评审评论 + 评审摘要;缺省给 PR 上的普通(issue)评论
39
+ const payload = reviews
40
+ ? {
41
+ reviews: await gh.reads.pullReviews(owner, repoName, pullNumber, signal),
42
+ comments: await gh.reads.pullReviewComments(owner, repoName, pullNumber, signal),
43
+ }
44
+ : {
45
+ comments: await gh.reads.issueComments(owner, repoName, pullNumber, signal),
46
+ };
52
47
 
53
- out = JSON.stringify(
54
- {
55
- reviews: reviewSummaries,
56
- comments: reviewComments,
57
- },
58
- null,
59
- 2,
60
- );
61
- } else {
62
- out = await ghExec(
63
- ["pr", "view", String(number), ...repoArgs(repo), "--json", "comments"],
64
- {
65
- cwd: ctx.cwd,
66
- signal,
67
- input: params,
68
- },
69
- );
70
- }
71
- const result = toStructuredJsonResult(out, params, prCommentsSchema);
48
+ const result = toStructuredJsonResult(
49
+ JSON.stringify(payload, null, 2),
50
+ params,
51
+ prCommentsSchema,
52
+ );
72
53
  result.details.pendant = subtitlePendant(params, "number");
73
54
  return result;
74
55
  },
@@ -3,8 +3,8 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- ghExec,
7
- repoArgs,
6
+ resolveRepo,
7
+ splitRepo,
8
8
  subtitlePendant,
9
9
  toToolResult,
10
10
  withStructuredResult,
@@ -12,7 +12,7 @@ import {
12
12
  import { parseDiffStats } from "../render.js";
13
13
  import { ghDiffPayloadSchema } from "../schemas.js";
14
14
 
15
- export function addReadPrDiffTool(_gh: GhClient, bus: ToolBus) {
15
+ export function addReadPrDiffTool(gh: GhClient, bus: ToolBus) {
16
16
  bus.register(
17
17
  defineStructuredTool({
18
18
  name: "read-github-pr-diff",
@@ -26,12 +26,13 @@ export function addReadPrDiffTool(_gh: GhClient, bus: ToolBus) {
26
26
  structuredSchema: ghDiffPayloadSchema,
27
27
  async execute(_id, params, signal, _onUpdate, ctx) {
28
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);
29
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
30
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
31
+ const diff = await gh.reads.pullDiff(owner, repoName, Number(number), signal);
32
+ const result = toToolResult(diff, params);
32
33
  result.details.pendant = subtitlePendant(params, "number");
33
- // 文本仍是原始 diff(`gh pr diff` 没有 --json),载荷从同一份 diff 解析出变更统计
34
- return withStructuredResult(result, { text: stdout, ...parseDiffStats(stdout) });
34
+ // 文本是原始 diff(没有 JSON 形式),载荷从同一份 diff 解析出变更统计
35
+ return withStructuredResult(result, { text: diff, ...parseDiffStats(diff) });
35
36
  },
36
37
  }),
37
38
  );
@@ -3,14 +3,14 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- ghExec,
7
- repoArgs,
6
+ resolveRepo,
7
+ splitRepo,
8
8
  subtitlePendant,
9
9
  toStructuredJsonResult,
10
10
  } from "../base.js";
11
11
  import { prViewSchema } from "../schemas.js";
12
12
 
13
- export function addReadPrTool(_gh: GhClient, bus: ToolBus) {
13
+ export function addReadPrTool(gh: GhClient, bus: ToolBus) {
14
14
  bus.register(
15
15
  defineStructuredTool({
16
16
  name: "read-github-pr",
@@ -24,18 +24,10 @@ export function addReadPrTool(_gh: GhClient, bus: ToolBus) {
24
24
  structuredSchema: prViewSchema,
25
25
  async execute(_id, params, signal, _onUpdate, ctx) {
26
26
  const { number, repo } = params;
27
- const out = await ghExec(
28
- [
29
- "pr",
30
- "view",
31
- String(number),
32
- ...repoArgs(repo),
33
- "--json",
34
- "title,state,body,author,createdAt,updatedAt,mergedAt,mergedBy,headRefName,baseRefName,url,additions,deletions,changedFiles,labels,assignees,reviewRequests,reviews,comments,number",
35
- ],
36
- { cwd: ctx.cwd, signal, input: params },
37
- );
38
- const result = toStructuredJsonResult(out, params, prViewSchema);
27
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
28
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
29
+ const data = await gh.reads.pull(owner, repoName, Number(number), signal);
30
+ const result = toStructuredJsonResult(JSON.stringify(data, null, 2), params, prViewSchema);
39
31
  result.details.pendant = subtitlePendant(params, "number");
40
32
  return result;
41
33
  },
@@ -4,8 +4,8 @@ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- ghExec,
8
- repoArgs,
7
+ resolveRepo,
8
+ splitRepo,
9
9
  subtitlePendant,
10
10
  toToolResult,
11
11
  withStructuredResult,
@@ -13,22 +13,7 @@ import {
13
13
  import { renderReleaseView } from "../render.js";
14
14
  import { ghReleaseViewPayloadSchema, ghReleaseViewSchema } from "../schemas.js";
15
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(",");
30
-
31
- export function addReadReleaseTool(_gh: GhClient, bus: ToolBus) {
16
+ export function addReadReleaseTool(gh: GhClient, bus: ToolBus) {
32
17
  bus.register(
33
18
  defineStructuredTool({
34
19
  name: "read-github-release",
@@ -43,11 +28,10 @@ export function addReadReleaseTool(_gh: GhClient, bus: ToolBus) {
43
28
  structuredSchema: ghReleaseViewPayloadSchema,
44
29
  async execute(_id, params, signal, _onUpdate, ctx) {
45
30
  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));
31
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
32
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
33
+ const data = await gh.reads.release(owner, repoName, tag, signal);
34
+ const view = parseWithSchema(ghReleaseViewSchema, data);
51
35
  const text = renderReleaseView(view);
52
36
  const result = toToolResult(text, params);
53
37
  result.details.pendant = subtitlePendant(params, "tag");
@@ -4,7 +4,8 @@ import { parseWithSchema } from "../../lib/parse-with-schema.js";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- ghExec,
7
+ resolveRepo,
8
+ splitRepo,
8
9
  subtitlePendant,
9
10
  toToolResult,
10
11
  withStructuredResult,
@@ -12,30 +13,7 @@ import {
12
13
  import { renderRepoView } from "../render.js";
13
14
  import { ghRepoPayloadSchema, ghRepoViewSchema } from "../schemas.js";
14
15
 
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(",");
37
-
38
- export function addReadRepoTool(_gh: GhClient, bus: ToolBus) {
16
+ export function addReadRepoTool(gh: GhClient, bus: ToolBus) {
39
17
  bus.register(
40
18
  defineStructuredTool({
41
19
  name: "read-github-repo",
@@ -48,13 +26,10 @@ export function addReadRepoTool(_gh: GhClient, bus: ToolBus) {
48
26
  structuredSchema: ghRepoPayloadSchema,
49
27
  async execute(_id, params, signal, _onUpdate, ctx) {
50
28
  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));
29
+ const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
30
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
31
+ const data = await gh.reads.repository(owner, repoName, signal);
32
+ const view = parseWithSchema(ghRepoViewSchema, data);
58
33
  const text = renderRepoView(view);
59
34
  const result = toToolResult(text, params);
60
35
  result.details.pendant = subtitlePendant(params);
@@ -4,7 +4,6 @@ import { Value } from "typebox/value";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- ghExec,
8
7
  resolveRepo,
9
8
  splitRepo,
10
9
  type StructuredResultOf,
@@ -20,7 +19,8 @@ interface PrChecksWaitParams {
20
19
  fail_fast?: boolean;
21
20
  }
22
21
 
23
- const prHeadSchema = Type.Object({ headRefOid: Type.String() });
22
+ /** pulls.get 里我们只需要 head commit 的 SHA(它决定查哪个 commit 的 checks)。 */
23
+ const pullHeadSchema = Type.Object({ head: Type.Object({ sha: Type.String() }) });
24
24
 
25
25
  /** The toolcall handler behind `wait-github-pr-checks`. */
26
26
  async function waitPrChecks(
@@ -39,18 +39,17 @@ async function waitPrChecks(
39
39
  const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
40
40
  const { owner, repo: repoName } = splitRepo(effectiveRepo);
41
41
 
42
- const prOut = await ghExec(
43
- ["pr", "view", String(number), "--repo", effectiveRepo, "--json", "headRefOid"],
44
- { cwd: ctx.cwd, signal, input: params },
42
+ const pull = Value.Parse(
43
+ pullHeadSchema,
44
+ await gh.reads.pull(owner, repoName, Number(number), signal),
45
45
  );
46
- const { headRefOid } = Value.Parse(prHeadSchema, JSON.parse(prOut));
47
46
 
48
47
  return waitChecksReport({
49
48
  checks: gh.checks,
50
49
  subject: `PR #${number}`,
51
50
  owner,
52
51
  repo: repoName,
53
- headSha: headRefOid,
52
+ headSha: pull.head.sha,
54
53
  failFast: fail_fast === true,
55
54
  signal,
56
55
  onUpdate,
@@ -1,16 +1,18 @@
1
1
  import { Type } from "typebox";
2
2
 
3
- import { runGh } from "../../lib/gh-process.js";
4
- import { parseWithSchema } from "../../lib/parse-with-schema.js";
5
3
  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";
4
+ import {
5
+ type GhClient,
6
+ renderRunStatus,
7
+ resolveRepo,
8
+ splitRepo,
9
+ subtitlePendant,
10
+ type ToolResult,
11
+ watchRun,
12
+ } from "../base.js";
13
+ import { ghRunPayloadSchema } from "../schemas.js";
8
14
 
9
- /** 收尾时查运行状态要的字段(`gh run watch` 自己没有 `--json`)。 */
10
- const RUN_FIELDS =
11
- "databaseId,displayTitle,status,conclusion,workflowName,headBranch,event,createdAt,updatedAt,startedAt,url";
12
-
13
- export function addWatchRunTool(_gh: GhClient, bus: ToolBus) {
15
+ export function addWatchRunTool(gh: GhClient, bus: ToolBus) {
14
16
  bus.register(
15
17
  defineStructuredTool({
16
18
  name: "watch-github-run",
@@ -28,33 +30,41 @@ export function addWatchRunTool(_gh: GhClient, bus: ToolBus) {
28
30
  const { run_id, repo } = params;
29
31
 
30
32
  const pendant = subtitlePendant(params, "run_id");
31
- onUpdate?.({
33
+ const publish = (message: ToolResult) => onUpdate?.(message);
34
+ publish({
32
35
  content: [{ type: "text", text: `Watching workflow run ${run_id}...` }],
33
36
  details: {},
34
37
  });
35
38
 
36
- const result = await runGh(["run", "watch", String(run_id), ...repoArgs(repo)], {
37
- cwd: ctx.cwd,
38
- signal,
39
- timeout: 600_000,
39
+ // 轮询循环要求非空 signal(与两个 wait 工具一致:没有就自建一个)
40
+ const pollSignal = signal ?? new AbortController().signal;
41
+ const effectiveRepo = await resolveRepo(repo, pollSignal, ctx.cwd, params);
42
+ const { owner, repo: repoName } = splitRepo(effectiveRepo);
43
+ const outcome = await watchRun({
44
+ owner,
45
+ repo: repoName,
46
+ runId: Number(run_id),
47
+ reads: gh.reads,
48
+ signal: pollSignal,
49
+ onUpdate: publish,
40
50
  });
41
51
 
42
- if (result.code !== 0) {
43
- throw new Error(`gh run watch failed: ${result.stderr || `exit code ${result.code}`}`);
52
+ if (outcome.outcome === "timeout") {
53
+ throw new Error(
54
+ `workflow run ${run_id} did not finish within ${Math.round(outcome.elapsedMs / 1000)}s; last status: ${renderRunStatus(outcome.run)}`,
55
+ );
44
56
  }
45
57
 
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));
58
+ const heading =
59
+ outcome.run.conclusion === "success"
60
+ ? `## Workflow Run ${run_id} Completed`
61
+ : `## Workflow Run ${run_id} Finished (${outcome.run.conclusion ?? "unknown"})`;
62
+ const text = `${heading}\n\n${renderRunStatus(outcome.run)}`;
53
63
 
54
64
  return {
55
65
  content: [{ type: "text" as const, text }],
56
- details: { exitCode: 0, input: params, ...(pendant && { pendant }) },
57
- structuredResult: { ok: true as const, value: { text, run } },
66
+ details: { input: params, ...(pendant && { pendant }) },
67
+ structuredResult: { ok: true as const, value: { text, run: outcome.run } },
58
68
  };
59
69
  },
60
70
  }),