@trim21/personal-pi-extensions 0.1.689 → 0.1.691

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.
Files changed (48) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/src/bwrap/dcg-scan.ts +2 -8
  4. package/src/bwrap/html.ts +13 -0
  5. package/src/bwrap/network-stack.ts +2 -1
  6. package/src/bwrap/runtime.ts +1 -8
  7. package/src/claude-code/grep.ts +2 -1
  8. package/src/claude-code/shell.ts +13 -42
  9. package/src/codemode/declarations.ts +3 -8
  10. package/src/codemode/worker.js +11 -7
  11. package/src/codemode/worker.ts +10 -6
  12. package/src/gh/base.ts +91 -23
  13. package/src/gh/index.ts +8 -5
  14. package/src/gh/tools/download-release-assets.ts +6 -3
  15. package/src/gh/tools/get-workflow-jobs.ts +4 -7
  16. package/src/gh/tools/list-issues.ts +6 -39
  17. package/src/gh/tools/list-prs.ts +6 -39
  18. package/src/gh/tools/list-releases.ts +6 -9
  19. package/src/gh/tools/list-workflow-runs.ts +6 -9
  20. package/src/gh/tools/read-ci-logs.ts +8 -19
  21. package/src/gh/tools/read-issue-comments.ts +4 -12
  22. package/src/gh/tools/read-issue.ts +4 -12
  23. package/src/gh/tools/read-pr-comments.ts +6 -14
  24. package/src/gh/tools/read-pr-diff.ts +8 -8
  25. package/src/gh/tools/read-pr-status.ts +6 -4
  26. package/src/gh/tools/read-pr.ts +4 -12
  27. package/src/gh/tools/read-release.ts +5 -8
  28. package/src/gh/tools/read-repo.ts +5 -9
  29. package/src/gh/tools/wait-commit-checks.ts +2 -4
  30. package/src/gh/tools/wait-pr-checks.ts +2 -4
  31. package/src/gh/tools/watch-run.ts +7 -4
  32. package/src/lib/abort.ts +10 -0
  33. package/src/lib/bash-tool.ts +48 -0
  34. package/src/lib/github-reads.ts +3 -23
  35. package/src/lib/github.ts +81 -41
  36. package/src/lib/lsp/client.ts +1 -1
  37. package/src/lib/lsp/server-config.ts +1 -0
  38. package/src/lib/model-call.ts +3 -7
  39. package/src/lib/tools-config.ts +2 -4
  40. package/src/opencode/bash.ts +11 -42
  41. package/src/opencode/files.ts +1 -0
  42. package/src/opencode/glob.ts +1 -6
  43. package/src/opencode/grep.ts +1 -6
  44. package/src/opencode/ripgrep.ts +5 -0
  45. package/src/talk/policy.ts +4 -4
  46. package/src/web/fetch.ts +2 -6
  47. package/src/web/search.ts +2 -6
  48. package/src/orphan-worker-cleaner.ts +0 -38
@@ -1,20 +1,19 @@
1
1
  /**
2
- * `list-github-issues`:两条取数分支(关键词搜索走 octokit,浏览走 `gh issue list --json`)
3
- * 归一成同一套 `SearchHit`,因此文本渲染与结构化载荷只有一份实现。
2
+ * `list-github-issues`:两条取数分支(关键词搜索走 octokit 的 `/search/issues`,浏览走
3
+ * REST 列表端点)归一成同一套 `SearchHit`,因此文本渲染与结构化载荷只有一份实现。
4
4
  */
5
5
 
6
- import { Type } from "typebox";
7
-
8
6
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
9
7
  import {
10
8
  browseList,
11
9
  type GhClient,
10
+ listFilterParameters,
12
11
  type ListFilters,
13
12
  renderHitList,
14
13
  type StructuredResultOf,
15
- subtitlePendant,
16
14
  type ToolCall,
17
15
  toToolResult,
16
+ withPendant,
18
17
  withStructuredResult,
19
18
  } from "../base.js";
20
19
  import { ghHitListPayloadSchema } from "../schemas.js";
@@ -29,9 +28,8 @@ async function listIssues(
29
28
  : await browseList(gh, "issue", params, { cwd: ctx.cwd, signal, input: params });
30
29
  const text = renderHitList("issue", hits, { repo, fields: params.fields });
31
30
  const result = toToolResult(text, params);
32
- result.details.pendant = subtitlePendant(params);
33
31
  // 文本可能被行数/字节预算截断,载荷给全部命中
34
- return withStructuredResult(result, { text, items: hits });
32
+ return withStructuredResult(withPendant(result, params), { text, items: hits });
35
33
  }
36
34
 
37
35
  export function addListIssuesTool(gh: GhClient, bus: ToolBus) {
@@ -42,38 +40,7 @@ export function addListIssuesTool(gh: GhClient, bus: ToolBus) {
42
40
  description:
43
41
  '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
42
  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
- }),
43
+ parameters: listFilterParameters("issue"),
77
44
  structuredSchema: ghHitListPayloadSchema,
78
45
  async execute(_id, params, signal, onUpdate, ctx) {
79
46
  return listIssues(gh, { params, ctx, signal, onUpdate });
@@ -1,20 +1,19 @@
1
1
  /**
2
- * `list-github-prs`:与 `list-github-issues` 同一套两条分支(关键词搜索走 octokit,
3
- * 浏览走 `gh pr list --json`),归一成同一套 `SearchHit`。
2
+ * `list-github-prs`:与 `list-github-issues` 同一套两条分支(关键词搜索走 octokit 的
3
+ * `/search/issues`,浏览走 REST 列表端点),归一成同一套 `SearchHit`。
4
4
  */
5
5
 
6
- import { Type } from "typebox";
7
-
8
6
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
9
7
  import {
10
8
  browseList,
11
9
  type GhClient,
10
+ listFilterParameters,
12
11
  type ListFilters,
13
12
  renderHitList,
14
13
  type StructuredResultOf,
15
- subtitlePendant,
16
14
  type ToolCall,
17
15
  toToolResult,
16
+ withPendant,
18
17
  withStructuredResult,
19
18
  } from "../base.js";
20
19
  import { ghHitListPayloadSchema } from "../schemas.js";
@@ -29,9 +28,8 @@ async function listPrs(
29
28
  : await browseList(gh, "pr", params, { cwd: ctx.cwd, signal, input: params });
30
29
  const text = renderHitList("pr", hits, { repo, fields: params.fields });
31
30
  const result = toToolResult(text, params);
32
- result.details.pendant = subtitlePendant(params);
33
31
  // 文本可能被行数/字节预算截断,载荷给全部命中
34
- return withStructuredResult(result, { text, items: hits });
32
+ return withStructuredResult(withPendant(result, params), { text, items: hits });
35
33
  }
36
34
 
37
35
  export function addListPrsTool(gh: GhClient, bus: ToolBus) {
@@ -42,38 +40,7 @@ export function addListPrsTool(gh: GhClient, bus: ToolBus) {
42
40
  description:
43
41
  '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
42
  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
- }),
43
+ parameters: listFilterParameters("pr"),
77
44
  structuredSchema: ghHitListPayloadSchema,
78
45
  async execute(_id, params, signal, onUpdate, ctx) {
79
46
  return listPrs(gh, { params, ctx, signal, onUpdate });
@@ -4,10 +4,9 @@ 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
- resolveRepo,
8
- splitRepo,
9
- subtitlePendant,
7
+ resolveRepoTarget,
10
8
  toToolResult,
9
+ withPendant,
11
10
  withStructuredResult,
12
11
  } from "../base.js";
13
12
  import { renderReleaseList } from "../render.js";
@@ -27,10 +26,9 @@ export function addListReleasesTool(gh: GhClient, bus: ToolBus) {
27
26
  }),
28
27
  structuredSchema: ghReleaseListPayloadSchema,
29
28
  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);
29
+ const { limit } = params;
30
+ const { owner, name } = await resolveRepoTarget(params.repo, signal, ctx.cwd, params);
31
+ const items = await gh.reads.listReleases(owner, name, limit, signal);
34
32
  const releases = parseWithSchema(Type.Array(ghReleaseSummarySchema), items);
35
33
  // REST 没有 isLatest:按列表顺序取第一个非 draft / prerelease 的条目
36
34
  const latestIndex = releases.findIndex(
@@ -40,8 +38,7 @@ export function addListReleasesTool(gh: GhClient, bus: ToolBus) {
40
38
  releases.map((release, index) => ({ ...release, latest: index === latestIndex })),
41
39
  );
42
40
  const result = toToolResult(text, params);
43
- result.details.pendant = subtitlePendant(params);
44
- return withStructuredResult(result, { text, releases });
41
+ return withStructuredResult(withPendant(result, params), { text, releases });
45
42
  },
46
43
  }),
47
44
  );
@@ -4,10 +4,9 @@ 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
- resolveRepo,
8
- splitRepo,
9
- subtitlePendant,
7
+ resolveRepoTarget,
10
8
  toToolResult,
9
+ withPendant,
11
10
  withStructuredResult,
12
11
  } from "../base.js";
13
12
  import { renderRunList } from "../render.js";
@@ -31,15 +30,13 @@ export function addListWorkflowRunsTool(gh: GhClient, bus: ToolBus) {
31
30
  }),
32
31
  structuredSchema: ghRunListPayloadSchema,
33
32
  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);
33
+ const { limit, status, workflow } = params;
34
+ const { owner, name } = await resolveRepoTarget(params.repo, signal, ctx.cwd, params);
35
+ const items = await gh.reads.listRuns(owner, name, { workflow, status, limit }, signal);
38
36
  const runs = parseWithSchema(Type.Array(ghRunSummarySchema), items);
39
37
  const text = renderRunList(runs);
40
38
  const result = toToolResult(text, params);
41
- result.details.pendant = subtitlePendant(params);
42
- return withStructuredResult(result, { text, runs });
39
+ return withStructuredResult(withPendant(result, params), { text, runs });
43
40
  },
44
41
  }),
45
42
  );
@@ -9,12 +9,12 @@ import { dirname, join } from "node:path";
9
9
 
10
10
  import { Type } from "typebox";
11
11
 
12
- import type { RunJob } from "../../lib/github.js";
12
+ import { isNotFound, type RunJob } from "../../lib/github.js";
13
13
  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
- resolveRepo,
17
+ resolveRepoTarget,
18
18
  splitRepo,
19
19
  structuredFailure,
20
20
  type StructuredFailureResult,
@@ -299,19 +299,6 @@ export function stepLineSpans(log: string, apiSteps: readonly StepRef[]): Map<nu
299
299
  return spans;
300
300
  }
301
301
 
302
- /** Text of `stepNumber` in its raw log, or null when the step never ran. */
303
- export function extractStepFromLog(
304
- log: string,
305
- stepNumber: number,
306
- apiSteps: readonly { number: number; name: string }[],
307
- ): string | null {
308
- const span = stepLineSpans(log, apiSteps).get(stepNumber);
309
- if (span === undefined) {
310
- return null;
311
- }
312
- return log.split("\n").slice(span.start, span.end).join("\n").trimEnd();
313
- }
314
-
315
302
  /** One step of a job, indexed into the job's raw log file. */
316
303
  export interface CiLogsStepIndex {
317
304
  number: number;
@@ -374,8 +361,11 @@ async function ciLogs(gh: GhClient, call: ToolCall<JobIdParams>): Promise<CiLogs
374
361
  const jobId = toPositiveId(job_id, "job_id");
375
362
 
376
363
  const pendant = subtitlePendant(params, "job_id");
377
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
378
- const { owner, repo: name } = splitRepo(effectiveRepo);
364
+ const {
365
+ fullName: effectiveRepo,
366
+ owner,
367
+ name,
368
+ } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
379
369
 
380
370
  const failure = (text: string): StructuredFailureResult => ({
381
371
  content: [{ type: "text", text }],
@@ -387,8 +377,7 @@ async function ciLogs(gh: GhClient, call: ToolCall<JobIdParams>): Promise<CiLogs
387
377
  try {
388
378
  target = await gh.checks.job(owner, name, jobId, signal);
389
379
  } catch (error) {
390
- const status = (error as { status?: number }).status;
391
- if (status !== 404) {
380
+ if (!isNotFound(error)) {
392
381
  throw error;
393
382
  }
394
383
  return failure(
@@ -1,13 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
- import {
5
- type GhClient,
6
- resolveRepo,
7
- splitRepo,
8
- subtitlePendant,
9
- toStructuredJsonResult,
10
- } from "../base.js";
4
+ import { type GhClient, resolveRepoTarget, toStructuredJsonResult, withPendant } from "../base.js";
11
5
  import { issueCommentsSchema } from "../schemas.js";
12
6
 
13
7
  export function addReadIssueCommentsTool(gh: GhClient, bus: ToolBus) {
@@ -24,16 +18,14 @@ export function addReadIssueCommentsTool(gh: GhClient, bus: ToolBus) {
24
18
  structuredSchema: issueCommentsSchema,
25
19
  async execute(_id, params, signal, _onUpdate, ctx) {
26
20
  const { number, repo } = 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);
21
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
22
+ const comments = await gh.reads.issueComments(owner, name, Number(number), signal);
30
23
  const result = toStructuredJsonResult(
31
24
  JSON.stringify({ comments }, null, 2),
32
25
  params,
33
26
  issueCommentsSchema,
34
27
  );
35
- result.details.pendant = subtitlePendant(params, "number");
36
- return result;
28
+ return withPendant(result, params, "number");
37
29
  },
38
30
  }),
39
31
  );
@@ -1,13 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
- import {
5
- type GhClient,
6
- resolveRepo,
7
- splitRepo,
8
- subtitlePendant,
9
- toStructuredJsonResult,
10
- } from "../base.js";
4
+ import { type GhClient, resolveRepoTarget, toStructuredJsonResult, withPendant } from "../base.js";
11
5
  import { issueViewSchema } from "../schemas.js";
12
6
 
13
7
  export function addReadIssueTool(gh: GhClient, bus: ToolBus) {
@@ -24,16 +18,14 @@ export function addReadIssueTool(gh: GhClient, bus: ToolBus) {
24
18
  structuredSchema: issueViewSchema,
25
19
  async execute(_id, params, signal, _onUpdate, ctx) {
26
20
  const { number, repo } = 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);
21
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
22
+ const data = await gh.reads.issue(owner, name, Number(number), signal);
30
23
  const result = toStructuredJsonResult(
31
24
  JSON.stringify(data, null, 2),
32
25
  params,
33
26
  issueViewSchema,
34
27
  );
35
- result.details.pendant = subtitlePendant(params, "number");
36
- return result;
28
+ return withPendant(result, params, "number");
37
29
  },
38
30
  }),
39
31
  );
@@ -1,13 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
- import {
5
- type GhClient,
6
- resolveRepo,
7
- splitRepo,
8
- subtitlePendant,
9
- toStructuredJsonResult,
10
- } from "../base.js";
4
+ import { type GhClient, resolveRepoTarget, toStructuredJsonResult, withPendant } from "../base.js";
11
5
  import { prCommentsSchema } from "../schemas.js";
12
6
 
13
7
  export function addReadPrCommentsTool(gh: GhClient, bus: ToolBus) {
@@ -31,18 +25,17 @@ export function addReadPrCommentsTool(gh: GhClient, bus: ToolBus) {
31
25
  structuredSchema: prCommentsSchema,
32
26
  async execute(_id, params, signal, _onUpdate, ctx) {
33
27
  const { number, repo, reviews } = params;
34
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
35
- const { owner, repo: repoName } = splitRepo(effectiveRepo);
28
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
36
29
  const pullNumber = Number(number);
37
30
 
38
31
  // reviews=true 给行内评审评论 + 评审摘要;缺省给 PR 上的普通(issue)评论
39
32
  const payload = reviews
40
33
  ? {
41
- reviews: await gh.reads.pullReviews(owner, repoName, pullNumber, signal),
42
- comments: await gh.reads.pullReviewComments(owner, repoName, pullNumber, signal),
34
+ reviews: await gh.reads.pullReviews(owner, name, pullNumber, signal),
35
+ comments: await gh.reads.pullReviewComments(owner, name, pullNumber, signal),
43
36
  }
44
37
  : {
45
- comments: await gh.reads.issueComments(owner, repoName, pullNumber, signal),
38
+ comments: await gh.reads.issueComments(owner, name, pullNumber, signal),
46
39
  };
47
40
 
48
41
  const result = toStructuredJsonResult(
@@ -50,8 +43,7 @@ export function addReadPrCommentsTool(gh: GhClient, bus: ToolBus) {
50
43
  params,
51
44
  prCommentsSchema,
52
45
  );
53
- result.details.pendant = subtitlePendant(params, "number");
54
- return result;
46
+ return withPendant(result, params, "number");
55
47
  },
56
48
  }),
57
49
  );
@@ -3,10 +3,9 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- resolveRepo,
7
- splitRepo,
8
- subtitlePendant,
6
+ resolveRepoTarget,
9
7
  toToolResult,
8
+ withPendant,
10
9
  withStructuredResult,
11
10
  } from "../base.js";
12
11
  import { parseDiffStats } from "../render.js";
@@ -26,13 +25,14 @@ export function addReadPrDiffTool(gh: GhClient, bus: ToolBus) {
26
25
  structuredSchema: ghDiffPayloadSchema,
27
26
  async execute(_id, params, signal, _onUpdate, ctx) {
28
27
  const { number, repo } = 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);
28
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
29
+ const diff = await gh.reads.pullDiff(owner, name, Number(number), signal);
32
30
  const result = toToolResult(diff, params);
33
- result.details.pendant = subtitlePendant(params, "number");
34
31
  // 文本是原始 diff(没有 JSON 形式),载荷从同一份 diff 解析出变更统计
35
- return withStructuredResult(result, { text: diff, ...parseDiffStats(diff) });
32
+ return withStructuredResult(withPendant(result, params, "number"), {
33
+ text: diff,
34
+ ...parseDiffStats(diff),
35
+ });
36
36
  },
37
37
  }),
38
38
  );
@@ -4,8 +4,7 @@ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  mergeChecks,
7
- resolveRepo,
8
- splitRepo,
7
+ resolveRepoTarget,
9
8
  type StructuredResultOf,
10
9
  subtitlePendant,
11
10
  type ToolCall,
@@ -32,8 +31,11 @@ export async function prStatus(
32
31
  const { number, repo } = params;
33
32
  const pullNumber = toPositiveId(number, "number");
34
33
  const pendant = subtitlePendant(params, "number");
35
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
36
- const { owner, repo: name } = splitRepo(effectiveRepo);
34
+ const {
35
+ fullName: effectiveRepo,
36
+ owner,
37
+ name,
38
+ } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
37
39
 
38
40
  const pollSignal = signal ?? new AbortController().signal;
39
41
  const headSha = await gh.checks.pullHead(owner, name, pullNumber, pollSignal);
@@ -1,13 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
- import {
5
- type GhClient,
6
- resolveRepo,
7
- splitRepo,
8
- subtitlePendant,
9
- toStructuredJsonResult,
10
- } from "../base.js";
4
+ import { type GhClient, resolveRepoTarget, toStructuredJsonResult, withPendant } from "../base.js";
11
5
  import { prViewSchema } from "../schemas.js";
12
6
 
13
7
  export function addReadPrTool(gh: GhClient, bus: ToolBus) {
@@ -24,12 +18,10 @@ export function addReadPrTool(gh: GhClient, bus: ToolBus) {
24
18
  structuredSchema: prViewSchema,
25
19
  async execute(_id, params, signal, _onUpdate, ctx) {
26
20
  const { number, repo } = params;
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);
21
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
22
+ const data = await gh.reads.pull(owner, name, Number(number), signal);
30
23
  const result = toStructuredJsonResult(JSON.stringify(data, null, 2), params, prViewSchema);
31
- result.details.pendant = subtitlePendant(params, "number");
32
- return result;
24
+ return withPendant(result, params, "number");
33
25
  },
34
26
  }),
35
27
  );
@@ -4,10 +4,9 @@ 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
- resolveRepo,
8
- splitRepo,
9
- subtitlePendant,
7
+ resolveRepoTarget,
10
8
  toToolResult,
9
+ withPendant,
11
10
  withStructuredResult,
12
11
  } from "../base.js";
13
12
  import { renderReleaseView } from "../render.js";
@@ -28,14 +27,12 @@ export function addReadReleaseTool(gh: GhClient, bus: ToolBus) {
28
27
  structuredSchema: ghReleaseViewPayloadSchema,
29
28
  async execute(_id, params, signal, _onUpdate, ctx) {
30
29
  const { tag, repo } = params;
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);
30
+ const { owner, name } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
31
+ const data = await gh.reads.release(owner, name, tag, signal);
34
32
  const view = parseWithSchema(ghReleaseViewSchema, data);
35
33
  const text = renderReleaseView(view);
36
34
  const result = toToolResult(text, params);
37
- result.details.pendant = subtitlePendant(params, "tag");
38
- return withStructuredResult(result, { text, release: view });
35
+ return withStructuredResult(withPendant(result, params, "tag"), { text, release: view });
39
36
  },
40
37
  }),
41
38
  );
@@ -4,10 +4,9 @@ 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
- resolveRepo,
8
- splitRepo,
9
- subtitlePendant,
7
+ resolveRepoTarget,
10
8
  toToolResult,
9
+ withPendant,
11
10
  withStructuredResult,
12
11
  } from "../base.js";
13
12
  import { renderRepoView } from "../render.js";
@@ -25,15 +24,12 @@ export function addReadRepoTool(gh: GhClient, bus: ToolBus) {
25
24
  }),
26
25
  structuredSchema: ghRepoPayloadSchema,
27
26
  async execute(_id, params, signal, _onUpdate, ctx) {
28
- const { repo } = params;
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);
27
+ const { owner, name } = await resolveRepoTarget(params.repo, signal, ctx.cwd, params);
28
+ const data = await gh.reads.repository(owner, name, signal);
32
29
  const view = parseWithSchema(ghRepoViewSchema, data);
33
30
  const text = renderRepoView(view);
34
31
  const result = toToolResult(text, params);
35
- result.details.pendant = subtitlePendant(params);
36
- return withStructuredResult(result, { text, repo: view });
32
+ return withStructuredResult(withPendant(result, params), { text, repo: view });
37
33
  },
38
34
  }),
39
35
  );
@@ -3,8 +3,7 @@ import { Type } from "typebox";
3
3
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
- resolveRepo,
7
- splitRepo,
6
+ resolveRepoTarget,
8
7
  type StructuredResultOf,
9
8
  subtitlePendant,
10
9
  type ToolCall,
@@ -33,8 +32,7 @@ async function waitCommitChecks(
33
32
  details: {},
34
33
  });
35
34
 
36
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
37
- const { owner, repo: repoName } = splitRepo(effectiveRepo);
35
+ const { owner, name: repoName } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
38
36
 
39
37
  const pollSignal = signal ?? new AbortController().signal;
40
38
  const sha = await gh.checks.headSha(owner, repoName, String(commit), pollSignal);
@@ -4,8 +4,7 @@ import { Value } from "typebox/value";
4
4
  import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
5
5
  import {
6
6
  type GhClient,
7
- resolveRepo,
8
- splitRepo,
7
+ resolveRepoTarget,
9
8
  type StructuredResultOf,
10
9
  subtitlePendant,
11
10
  type ToolCall,
@@ -36,8 +35,7 @@ async function waitPrChecks(
36
35
  details: {},
37
36
  });
38
37
 
39
- const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
40
- const { owner, repo: repoName } = splitRepo(effectiveRepo);
38
+ const { owner, name: repoName } = await resolveRepoTarget(repo, signal, ctx.cwd, params);
41
39
 
42
40
  const pull = Value.Parse(
43
41
  pullHeadSchema,
@@ -4,8 +4,7 @@ import { defineStructuredTool, type ToolBus } from "../../lib/tool-bus.js";
4
4
  import {
5
5
  type GhClient,
6
6
  renderRunStatus,
7
- resolveRepo,
8
- splitRepo,
7
+ resolveRepoTarget,
9
8
  subtitlePendant,
10
9
  type ToolResult,
11
10
  watchRun,
@@ -38,8 +37,12 @@ export function addWatchRunTool(gh: GhClient, bus: ToolBus) {
38
37
 
39
38
  // 轮询循环要求非空 signal(与两个 wait 工具一致:没有就自建一个)
40
39
  const pollSignal = signal ?? new AbortController().signal;
41
- const effectiveRepo = await resolveRepo(repo, pollSignal, ctx.cwd, params);
42
- const { owner, repo: repoName } = splitRepo(effectiveRepo);
40
+ const { owner, name: repoName } = await resolveRepoTarget(
41
+ repo,
42
+ pollSignal,
43
+ ctx.cwd,
44
+ params,
45
+ );
43
46
  const outcome = await watchRun({
44
47
  owner,
45
48
  repo: repoName,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 把调用方的取消信号与一个本地超时合并成单个 AbortSignal。
3
+ *
4
+ * 调用方可能不传 signal(例如 agent 空闲时的 `ctx.signal`),那时仍然要有超时兜底,
5
+ * 所以本地 `AbortSignal.timeout` 始终生效,两者都触发时以先到者为准。
6
+ */
7
+ export function withTimeoutSignal(signal: AbortSignal | undefined, ms: number): AbortSignal {
8
+ const timeout = AbortSignal.timeout(ms);
9
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
10
+ }