@trim21/personal-pi-extensions 0.1.684 → 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.
@@ -1,64 +1,83 @@
1
+ /**
2
+ * `list-github-issues`:两条取数分支(关键词搜索走 octokit,浏览走 `gh issue list --json`)
3
+ * 归一成同一套 `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 listIssues(gh: GhClient, call: ToolCall<ListFilters>): Promise<ToolResult> {
22
+ async function listIssues(
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("issue", params, gh.search)
20
- : await listGithub("issue", 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("issue", params) }
29
+ : await browseList(gh, "issue", params, { cwd: ctx.cwd, signal, input: params });
30
+ const text = renderHitList("issue", 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 addListIssuesTool(gh: GhClient, bus: ToolBus) {
28
- bus.register({
29
- name: "list-github-issues",
30
- label: "GitHub Issues List",
31
- description:
32
- 'List GitHub issues with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open issues — pass state="all" to include closed ones. Set fields to choose the columns of each result row.',
33
- promptSnippet: "List or search GitHub issues",
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, all (default: open; all applies to keyword search and covers closed too)",
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",
57
- }),
58
- ),
38
+ bus.register(
39
+ defineStructuredTool({
40
+ name: "list-github-issues",
41
+ label: "GitHub Issues List",
42
+ description:
43
+ 'List GitHub issues with optional filters and keyword search. When repo is omitted, keyword search runs across GitHub. Keyword search defaults to open issues — pass state="all" to include closed ones. Set fields to choose the columns of each result row.',
44
+ promptSnippet: "List or search GitHub issues",
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, all (default: open; all applies to keyword search and covers closed too)",
52
+ }),
53
+ ),
54
+ label: Type.Optional(Type.String({ description: "Filter by label" })),
55
+ // `@me` 只在关键词搜索分支可用:搜索 API 自己把它解析成当前登录用户。浏览分支走
56
+ // REST 列表端点,`@me` 按字面量转发,不会匹配到当前用户(本仓库不做展开)。
57
+ author: Type.Optional(
58
+ Type.String({
59
+ description: "Filter by author ('@me' works with keywords; browse takes it literally)",
60
+ }),
61
+ ),
62
+ assignee: Type.Optional(
63
+ Type.String({
64
+ description:
65
+ "Filter by assignee ('@me' works with keywords; browse takes it literally)",
66
+ }),
67
+ ),
68
+ milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
69
+ limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
70
+ fields: Type.Optional(
71
+ Type.String({
72
+ description:
73
+ "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",
74
+ }),
75
+ ),
76
+ }),
77
+ structuredSchema: ghHitListPayloadSchema,
78
+ async execute(_id, params, signal, onUpdate, ctx) {
79
+ return listIssues(gh, { params, ctx, signal, onUpdate });
80
+ },
59
81
  }),
60
- async execute(_id, params, signal, onUpdate, ctx) {
61
- return listIssues(gh, { params, ctx, signal, onUpdate });
62
- },
63
- });
82
+ );
64
83
  }
@@ -1,64 +1,83 @@
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(gh, "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` 只在关键词搜索分支可用:搜索 API 自己把它解析成当前登录用户。浏览分支走
56
+ // REST 列表端点,`@me` 按字面量转发,不会匹配到当前用户(本仓库不做展开)。
57
+ author: Type.Optional(
58
+ Type.String({
59
+ description: "Filter by author ('@me' works with keywords; browse takes it literally)",
60
+ }),
61
+ ),
62
+ assignee: Type.Optional(
63
+ Type.String({
64
+ description:
65
+ "Filter by assignee ('@me' works with keywords; browse takes it literally)",
66
+ }),
67
+ ),
68
+ milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
69
+ limit: Type.Optional(Type.Number({ description: "Max results (default 30, max 100)" })),
70
+ fields: Type.Optional(
71
+ Type.String({
72
+ description:
73
+ "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",
74
+ }),
75
+ ),
76
+ }),
77
+ structuredSchema: ghHitListPayloadSchema,
78
+ async execute(_id, params, signal, onUpdate, ctx) {
79
+ return listPrs(gh, { params, ctx, signal, onUpdate });
80
+ },
59
81
  }),
60
- async execute(_id, params, signal, onUpdate, ctx) {
61
- return listPrs(gh, { params, ctx, signal, onUpdate });
62
- },
63
- });
82
+ );
64
83
  }
@@ -1,30 +1,48 @@
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
+ resolveRepo,
8
+ splitRepo,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderReleaseList } from "../render.js";
14
+ import { ghReleaseListPayloadSchema, ghReleaseSummarySchema } from "../schemas.js";
5
15
 
6
- 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)" })),
16
+ export function addListReleasesTool(gh: GhClient, bus: ToolBus) {
17
+ bus.register(
18
+ defineStructuredTool({
19
+ name: "list-github-releases",
20
+ label: "GitHub Releases List",
21
+ description:
22
+ "List GitHub releases. Each row is: tag, flags (latest/prerelease/draft), published date, name.",
23
+ promptSnippet: "List GitHub releases",
24
+ parameters: Type.Object({
25
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
26
+ limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
27
+ }),
28
+ structuredSchema: ghReleaseListPayloadSchema,
29
+ async execute(_id, params, signal, _onUpdate, ctx) {
30
+ const { repo, limit } = params;
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
+ );
42
+ const result = toToolResult(text, params);
43
+ result.details.pendant = subtitlePendant(params);
44
+ return withStructuredResult(result, { text, releases });
45
+ },
15
46
  }),
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
- });
47
+ );
30
48
  }
@@ -1,40 +1,46 @@
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
+ resolveRepo,
8
+ splitRepo,
9
+ subtitlePendant,
10
+ toToolResult,
11
+ withStructuredResult,
12
+ } from "../base.js";
13
+ import { renderRunList } from "../render.js";
14
+ import { ghRunListPayloadSchema, ghRunSummarySchema } from "../schemas.js";
5
15
 
6
- 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" })),
16
+ export function addListWorkflowRunsTool(gh: GhClient, bus: ToolBus) {
17
+ bus.register(
18
+ defineStructuredTool({
19
+ name: "list-github-workflow-runs",
20
+ label: "GitHub Workflow Runs",
21
+ description:
22
+ "List GitHub Actions workflow runs. Each row is: id, status, conclusion, workflow, branch, event, created date, url.",
23
+ promptSnippet: "List GitHub workflow runs",
24
+ parameters: Type.Object({
25
+ repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
26
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
27
+ status: Type.Optional(
28
+ Type.String({ description: "Filter by status: success, failure, cancelled, etc." }),
29
+ ),
30
+ workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
31
+ }),
32
+ structuredSchema: ghRunListPayloadSchema,
33
+ async execute(_id, params, signal, _onUpdate, ctx) {
34
+ const { repo, limit, status, workflow } = params;
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);
39
+ const text = renderRunList(runs);
40
+ const result = toToolResult(text, params);
41
+ result.details.pendant = subtitlePendant(params);
42
+ return withStructuredResult(result, { text, runs });
43
+ },
19
44
  }),
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
- });
45
+ );
40
46
  }
@@ -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
  },