@trim21/personal-pi-extensions 0.1.690 → 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 (40) hide show
  1. package/README.md +3 -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/gh/base.ts +91 -23
  9. package/src/gh/index.ts +8 -5
  10. package/src/gh/tools/download-release-assets.ts +6 -3
  11. package/src/gh/tools/get-workflow-jobs.ts +4 -7
  12. package/src/gh/tools/list-issues.ts +6 -39
  13. package/src/gh/tools/list-prs.ts +6 -39
  14. package/src/gh/tools/list-releases.ts +6 -9
  15. package/src/gh/tools/list-workflow-runs.ts +6 -9
  16. package/src/gh/tools/read-ci-logs.ts +8 -19
  17. package/src/gh/tools/read-issue-comments.ts +4 -12
  18. package/src/gh/tools/read-issue.ts +4 -12
  19. package/src/gh/tools/read-pr-comments.ts +6 -14
  20. package/src/gh/tools/read-pr-diff.ts +8 -8
  21. package/src/gh/tools/read-pr-status.ts +6 -4
  22. package/src/gh/tools/read-pr.ts +4 -12
  23. package/src/gh/tools/read-release.ts +5 -8
  24. package/src/gh/tools/read-repo.ts +5 -9
  25. package/src/gh/tools/wait-commit-checks.ts +2 -4
  26. package/src/gh/tools/wait-pr-checks.ts +2 -4
  27. package/src/gh/tools/watch-run.ts +7 -4
  28. package/src/lib/abort.ts +10 -0
  29. package/src/lib/bash-tool.ts +48 -0
  30. package/src/lib/github-reads.ts +3 -23
  31. package/src/lib/github.ts +81 -41
  32. package/src/lib/model-call.ts +3 -7
  33. package/src/lib/tools-config.ts +2 -4
  34. package/src/opencode/bash.ts +11 -42
  35. package/src/opencode/glob.ts +1 -6
  36. package/src/opencode/grep.ts +1 -6
  37. package/src/opencode/ripgrep.ts +5 -0
  38. package/src/web/fetch.ts +2 -6
  39. package/src/web/search.ts +2 -6
  40. package/src/orphan-worker-cleaner.ts +0 -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 { 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
+ }
@@ -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
+ }
@@ -15,7 +15,7 @@ import { pipeline } from "node:stream/promises";
15
15
 
16
16
  import type { Octokit } from "octokit";
17
17
 
18
- import { createGithubApi, type GithubClientOptions } from "./github.js";
18
+ import { type GithubApi } from "./github.js";
19
19
 
20
20
  /** `actions.listWorkflowRuns*` 的 `status` 取值(octokit 是字面量联合,工具参数是 string)。 */
21
21
  type WorkflowRunParams = NonNullable<
@@ -146,23 +146,6 @@ function limitOf(limit: number | undefined, fallback: number): number {
146
146
  return Math.min(Math.max(limit ?? fallback, 1), 100);
147
147
  }
148
148
 
149
- /** 响应体不是 2xx 时抛错(状态、URL 与 GitHub 的 message 都在里面)。 */
150
- async function ensureOk(response: Response): Promise<Response> {
151
- if (response.ok) {
152
- return response;
153
- }
154
- let detail: string | undefined;
155
- try {
156
- const body = await response.text();
157
- detail = (JSON.parse(body) as { message?: string }).message ?? body.slice(0, 200);
158
- } catch {
159
- detail = undefined;
160
- }
161
- throw new Error(
162
- `GitHub API error (HTTP ${response.status}) at ${response.url}${detail === undefined || detail === "" ? "" : `: ${detail}`}`,
163
- );
164
- }
165
-
166
149
  function octokitRequest(signal: AbortSignal | undefined): { signal?: AbortSignal } {
167
150
  return signal === undefined ? {} : { signal };
168
151
  }
@@ -172,9 +155,7 @@ function asJson(value: unknown): unknown {
172
155
  return value;
173
156
  }
174
157
 
175
- export function createGithubReads(options: GithubClientOptions = {}): GithubReads {
176
- const api = createGithubApi(options);
177
-
158
+ export function createGithubReads(api: GithubApi): GithubReads {
178
159
  return {
179
160
  async issue(owner, repo, number, signal) {
180
161
  const { data } = await api.call((octokit) =>
@@ -373,11 +354,11 @@ export function createGithubReads(options: GithubClientOptions = {}): GithubRead
373
354
  },
374
355
 
375
356
  async downloadAssetTo(owner, repo, assetId, destPath, signal) {
357
+ // rawFetch 非 2xx 直接抛 GithubApiError;这里只需流式落盘
376
358
  const response = await api.rawFetch(
377
359
  `https://api.github.com/repos/${owner}/${repo}/releases/assets/${assetId}`,
378
360
  { headers: { accept: "application/octet-stream" }, ...(signal && { signal }) },
379
361
  );
380
- await ensureOk(response);
381
362
  await streamToFile(response, destPath);
382
363
  },
383
364
 
@@ -388,7 +369,6 @@ export function createGithubReads(options: GithubClientOptions = {}): GithubRead
388
369
  `https://api.github.com/repos/${owner}/${repo}/${kind}${suffix}`,
389
370
  { ...(signal && { signal }) },
390
371
  );
391
- await ensureOk(response);
392
372
  await streamToFile(response, destPath);
393
373
  },
394
374
  };