@trim21/personal-pi-extensions 0.1.690 → 0.1.692

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 (44) hide show
  1. package/README.md +11 -3
  2. package/package.json +1 -1
  3. package/src/bwrap/dcg-scan.ts +2 -8
  4. package/src/bwrap/html.ts +13 -0
  5. package/src/bwrap/runtime.ts +1 -8
  6. package/src/claude-code/shell.ts +12 -42
  7. package/src/codemode/declarations.ts +3 -8
  8. package/src/codemode/tool.ts +15 -6
  9. package/src/gh/base.ts +91 -23
  10. package/src/gh/index.ts +8 -5
  11. package/src/gh/tools/download-release-assets.ts +6 -3
  12. package/src/gh/tools/get-workflow-jobs.ts +4 -7
  13. package/src/gh/tools/list-issues.ts +6 -39
  14. package/src/gh/tools/list-prs.ts +6 -39
  15. package/src/gh/tools/list-releases.ts +6 -9
  16. package/src/gh/tools/list-workflow-runs.ts +6 -9
  17. package/src/gh/tools/read-ci-logs.ts +8 -19
  18. package/src/gh/tools/read-issue-comments.ts +4 -12
  19. package/src/gh/tools/read-issue.ts +4 -12
  20. package/src/gh/tools/read-pr-comments.ts +6 -14
  21. package/src/gh/tools/read-pr-diff.ts +8 -8
  22. package/src/gh/tools/read-pr-status.ts +6 -4
  23. package/src/gh/tools/read-pr.ts +4 -12
  24. package/src/gh/tools/read-release.ts +5 -8
  25. package/src/gh/tools/read-repo.ts +5 -9
  26. package/src/gh/tools/wait-commit-checks.ts +2 -4
  27. package/src/gh/tools/wait-pr-checks.ts +2 -4
  28. package/src/gh/tools/watch-run.ts +7 -4
  29. package/src/index.ts +3 -0
  30. package/src/lib/abort.ts +10 -0
  31. package/src/lib/bash-tool.ts +48 -0
  32. package/src/lib/github-reads.ts +3 -23
  33. package/src/lib/github.ts +81 -41
  34. package/src/lib/model-call.ts +3 -7
  35. package/src/lib/tool-bus.ts +30 -2
  36. package/src/lib/tool-registration.ts +6 -0
  37. package/src/lib/tools-config.ts +24 -7
  38. package/src/opencode/bash.ts +11 -42
  39. package/src/opencode/glob.ts +1 -6
  40. package/src/opencode/grep.ts +1 -6
  41. package/src/opencode/ripgrep.ts +5 -0
  42. package/src/web/fetch.ts +2 -6
  43. package/src/web/search.ts +2 -6
  44. package/src/orphan-worker-cleaner.ts +0 -38
@@ -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,
package/src/index.ts CHANGED
@@ -126,6 +126,9 @@ export default function personalExtensions(pi: ExtensionAPI): void {
126
126
  codemode.register(bus, { policy: services.policy, reads: fileToolset.reads }),
127
127
  );
128
128
 
129
+ // 注册期的可诊断问题(例如 codemode-only 命中但没有结构化输出的工具)与其它警告一起上报。
130
+ warnings.push(...registration.diagnostics());
131
+
129
132
  for (const warning of warnings) {
130
133
  ctx.ui.notify(warning, "warning");
131
134
  }
@@ -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
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * claude-code 与 opencode 两套 Bash 工具共用的部分:超时上下限、结构化结果 schema、
3
+ * 以及截断后从落盘文件读回完整输出。
4
+ *
5
+ * 只放两边逐字相同的部分。两套工具的截断提示文案、失败判定与 details 是刻意的差异,
6
+ * 留在各自的工具里,不在这里统一。
7
+ */
8
+
9
+ import { readFile } from "node:fs/promises";
10
+
11
+ import type { TruncationResult } from "@earendil-works/pi-coding-agent";
12
+ import { Type } from "typebox";
13
+
14
+ export const BASH_DEFAULT_TIMEOUT_MS = 120_000;
15
+ export const BASH_MAX_TIMEOUT_MS = 7_200_000;
16
+
17
+ /**
18
+ * `Bash` 的结构化结果:只有退出码与完整输出——命令非零退出不是失败,所以载荷里没有成败
19
+ * 标志,脚本直接读 `exitCode` 分支;超时/中止时 `exitCode` 为 `null`(原因在模型侧文本里)。
20
+ */
21
+ export const bashStructuredSchema = Type.Object({
22
+ exitCode: Type.Union([Type.Number(), Type.Null()], {
23
+ description: "Exit code of the command; null when it was killed (timeout or abort)",
24
+ }),
25
+ output: Type.String({
26
+ description:
27
+ "Complete output of the command (stdout and stderr merged). Never truncated, never mixed with tool-added notices.",
28
+ }),
29
+ });
30
+
31
+ /**
32
+ * 载荷里的输出:文本被截断时读回落盘的完整输出(读不到就退回那份截断文本),
33
+ * 因此脚本拿到的永远是命令真正输出的内容。
34
+ */
35
+ export async function spilledOutput(
36
+ output: string,
37
+ truncation: TruncationResult,
38
+ spillPath: string | undefined,
39
+ ): Promise<string> {
40
+ if (spillPath === undefined || !truncation.truncated) {
41
+ return output;
42
+ }
43
+ try {
44
+ return await readFile(spillPath, "utf8");
45
+ } catch {
46
+ return output;
47
+ }
48
+ }