@oh-my-pi/pi-coding-agent 14.6.6 → 14.7.0

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 (56) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/examples/hooks/handoff.ts +1 -1
  3. package/examples/hooks/qna.ts +1 -1
  4. package/examples/sdk/03-custom-prompt.ts +7 -4
  5. package/examples/sdk/README.md +1 -1
  6. package/package.json +7 -7
  7. package/src/autoresearch/index.ts +48 -44
  8. package/src/cli/read-cli.ts +58 -0
  9. package/src/cli.ts +1 -0
  10. package/src/commands/read.ts +40 -0
  11. package/src/commit/agentic/agent.ts +1 -1
  12. package/src/commit/analysis/conventional.ts +1 -1
  13. package/src/commit/analysis/summary.ts +1 -1
  14. package/src/commit/changelog/generate.ts +1 -1
  15. package/src/commit/map-reduce/map-phase.ts +1 -1
  16. package/src/commit/map-reduce/reduce-phase.ts +1 -1
  17. package/src/config/settings-schema.ts +39 -0
  18. package/src/edit/line-hash.ts +34 -4
  19. package/src/edit/modes/hashline.ts +201 -6
  20. package/src/edit/streaming.ts +4 -1
  21. package/src/export/html/index.ts +1 -1
  22. package/src/extensibility/extensions/runner.ts +3 -3
  23. package/src/extensibility/extensions/types.ts +4 -4
  24. package/src/main.ts +3 -3
  25. package/src/memories/index.ts +1 -1
  26. package/src/modes/components/agent-dashboard.ts +1 -1
  27. package/src/modes/components/read-tool-group.ts +4 -9
  28. package/src/modes/components/tool-execution.ts +4 -0
  29. package/src/modes/controllers/event-controller.ts +2 -0
  30. package/src/modes/rpc/rpc-types.ts +1 -1
  31. package/src/modes/utils/context-usage.ts +12 -5
  32. package/src/modes/utils/ui-helpers.ts +1 -0
  33. package/src/prompts/system/project-prompt.md +36 -0
  34. package/src/prompts/system/system-prompt.md +0 -29
  35. package/src/prompts/tools/github.md +1 -0
  36. package/src/prompts/tools/read.md +15 -14
  37. package/src/sdk.ts +29 -28
  38. package/src/session/agent-session.ts +20 -12
  39. package/src/session/compaction/branch-summarization.ts +1 -1
  40. package/src/session/compaction/compaction.ts +3 -3
  41. package/src/session/session-dump-format.ts +10 -5
  42. package/src/session/streaming-output.ts +1 -1
  43. package/src/system-prompt.ts +35 -3
  44. package/src/task/executor.ts +4 -3
  45. package/src/tools/fetch.ts +4 -4
  46. package/src/tools/gh.ts +187 -0
  47. package/src/tools/inspect-image.ts +1 -1
  48. package/src/tools/output-meta.ts +1 -1
  49. package/src/tools/path-utils.ts +11 -0
  50. package/src/tools/read.ts +388 -204
  51. package/src/tools/search.ts +1 -1
  52. package/src/tools/sqlite-reader.ts +1 -1
  53. package/src/utils/commit-message-generator.ts +1 -1
  54. package/src/utils/title-generator.ts +1 -1
  55. package/src/web/search/providers/anthropic.ts +1 -1
  56. package/src/workspace-tree.ts +396 -0
package/src/tools/gh.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import * as os from "node:os";
2
3
  import * as path from "node:path";
3
4
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
4
5
  import { StringEnum } from "@oh-my-pi/pi-ai";
@@ -151,6 +152,7 @@ const githubSchema = Type.Object({
151
152
  [
152
153
  "repo_view",
153
154
  "issue_view",
155
+ "pr_create",
154
156
  "pr_view",
155
157
  "pr_diff",
156
158
  "pr_checkout",
@@ -205,6 +207,53 @@ const githubSchema = Type.Object({
205
207
  ),
206
208
  force: Type.Optional(Type.Boolean({ description: "reset existing local branch (pr_checkout)" })),
207
209
  forceWithLease: Type.Optional(Type.Boolean({ description: "force-with-lease push (pr_push)" })),
210
+ title: Type.Optional(
211
+ Type.String({
212
+ description: "PR title (pr_create)",
213
+ examples: ["Fix login bug"],
214
+ }),
215
+ ),
216
+ body: Type.Optional(
217
+ Type.String({
218
+ description: "PR body markdown (pr_create); mutually exclusive with fill",
219
+ }),
220
+ ),
221
+ base: Type.Optional(
222
+ Type.String({
223
+ description: "PR base branch (pr_create); defaults to repo default branch",
224
+ examples: ["main"],
225
+ }),
226
+ ),
227
+ head: Type.Optional(
228
+ Type.String({
229
+ description: "PR head branch (pr_create); defaults to current branch",
230
+ examples: ["feature/foo"],
231
+ }),
232
+ ),
233
+ draft: Type.Optional(Type.Boolean({ description: "open PR as draft (pr_create)" })),
234
+ fill: Type.Optional(
235
+ Type.Boolean({
236
+ description: "auto-fill PR title/body from commits (pr_create); mutually exclusive with title/body",
237
+ }),
238
+ ),
239
+ reviewer: Type.Optional(
240
+ Type.Array(Type.String(), {
241
+ description: "reviewers to request (pr_create); accepts users or org/team",
242
+ examples: [["octocat", "myorg/team"]],
243
+ }),
244
+ ),
245
+ assignee: Type.Optional(
246
+ Type.Array(Type.String(), {
247
+ description: "assignees (pr_create); use @me for the authenticated user",
248
+ examples: [["@me"]],
249
+ }),
250
+ ),
251
+ label: Type.Optional(
252
+ Type.Array(Type.String(), {
253
+ description: "labels to apply (pr_create)",
254
+ examples: [["bug", "enhancement"]],
255
+ }),
256
+ ),
208
257
  query: Type.Optional(
209
258
  Type.String({
210
259
  description: "search query (search_issues, search_prs, search_code, search_commits, search_repos)",
@@ -2063,6 +2112,8 @@ export class GithubTool implements AgentTool<typeof githubSchema, GhToolDetails>
2063
2112
  return executeRepoView(this.session, params, signal);
2064
2113
  case "issue_view":
2065
2114
  return executeIssueView(this.session, params, signal);
2115
+ case "pr_create":
2116
+ return executePrCreate(this.session, params, signal);
2066
2117
  case "pr_view":
2067
2118
  return executePrView(this.session, params, signal);
2068
2119
  case "pr_diff":
@@ -2442,6 +2493,142 @@ async function executePrPush(
2442
2493
  );
2443
2494
  }
2444
2495
 
2496
+ async function executePrCreate(
2497
+ session: ToolSession,
2498
+ params: GithubInput,
2499
+ signal: AbortSignal | undefined,
2500
+ ): Promise<AgentToolResult<GhToolDetails>> {
2501
+ const repo = normalizeOptionalString(params.repo);
2502
+ const title = normalizeOptionalString(params.title);
2503
+ const body = params.body;
2504
+ const base = normalizeOptionalString(params.base);
2505
+ const head = normalizeOptionalString(params.head);
2506
+ const draft = params.draft ?? false;
2507
+ const fill = params.fill ?? false;
2508
+ const reviewers = normalizePrIdentifierList(params.reviewer);
2509
+ const assignees = normalizePrIdentifierList(params.assignee);
2510
+ const labels = normalizePrIdentifierList(params.label);
2511
+
2512
+ if (!fill && !title) {
2513
+ throw new ToolError("title is required unless fill is true");
2514
+ }
2515
+ if (fill && (title || body !== undefined)) {
2516
+ throw new ToolError("fill is mutually exclusive with title and body");
2517
+ }
2518
+
2519
+ const args = ["pr", "create"];
2520
+ appendRepoFlag(args, repo);
2521
+ if (title) args.push("--title", title);
2522
+ if (base) args.push("--base", base);
2523
+ if (head) args.push("--head", head);
2524
+ if (draft) args.push("--draft");
2525
+ if (fill) args.push("--fill");
2526
+ for (const reviewer of reviewers) args.push("--reviewer", reviewer);
2527
+ for (const assignee of assignees) args.push("--assignee", assignee);
2528
+ for (const label of labels) args.push("--label", label);
2529
+
2530
+ let bodyDir: string | undefined;
2531
+ try {
2532
+ if (!fill) {
2533
+ if (body !== undefined && body.length > 0) {
2534
+ // Route through a temp file so multi-KB bodies stay clear of any
2535
+ // argv-length limits and shell-quoting hazards on uncommon platforms.
2536
+ bodyDir = await fs.mkdtemp(path.join(os.tmpdir(), "gh-pr-body-"));
2537
+ const bodyFile = path.join(bodyDir, "body.md");
2538
+ await Bun.write(bodyFile, body);
2539
+ args.push("--body-file", bodyFile);
2540
+ } else {
2541
+ // Avoid gh dropping into an interactive editor when no body is given.
2542
+ args.push("--body", "");
2543
+ }
2544
+ }
2545
+
2546
+ const output = await git.github.text(session.cwd, args, signal, {
2547
+ repoProvided: Boolean(repo),
2548
+ });
2549
+ const url =
2550
+ output
2551
+ .split("\n")
2552
+ .map(line => line.trim())
2553
+ .find(line => line.startsWith("https://github.com/")) ?? output.trim();
2554
+ const parsed = parsePullRequestUrl(url);
2555
+ const resolvedRepo = repo ?? parsed.repo;
2556
+
2557
+ let prView: GhPrViewData | undefined;
2558
+ if (resolvedRepo && parsed.prNumber !== undefined) {
2559
+ try {
2560
+ prView = await git.github.json<GhPrViewData>(
2561
+ session.cwd,
2562
+ [
2563
+ "pr",
2564
+ "view",
2565
+ String(parsed.prNumber),
2566
+ "--repo",
2567
+ resolvedRepo,
2568
+ "--json",
2569
+ GH_PR_FIELDS_NO_COMMENTS.join(","),
2570
+ ],
2571
+ signal,
2572
+ { repoProvided: true },
2573
+ );
2574
+ } catch {
2575
+ // Best-effort summary; PR creation already succeeded.
2576
+ }
2577
+ }
2578
+
2579
+ const text = formatPrCreateResult({
2580
+ url,
2581
+ prNumber: parsed.prNumber,
2582
+ data: prView,
2583
+ title,
2584
+ base,
2585
+ head,
2586
+ draft,
2587
+ });
2588
+ return buildTextResult(text, url || prView?.url);
2589
+ } finally {
2590
+ if (bodyDir) {
2591
+ await fs.rm(bodyDir, { recursive: true, force: true }).catch(() => {});
2592
+ }
2593
+ }
2594
+ }
2595
+
2596
+ function formatPrCreateResult(options: {
2597
+ url: string;
2598
+ prNumber?: number;
2599
+ data?: GhPrViewData;
2600
+ title?: string;
2601
+ base?: string;
2602
+ head?: string;
2603
+ draft?: boolean;
2604
+ }): string {
2605
+ const number = options.prNumber ?? options.data?.number;
2606
+ const headerTitle = options.data?.title ?? options.title ?? "Untitled";
2607
+ const header =
2608
+ number !== undefined
2609
+ ? `# Created Pull Request #${number}: ${headerTitle}`
2610
+ : `# Created Pull Request: ${headerTitle}`;
2611
+ const lines: string[] = [header, ""];
2612
+ pushLine(lines, "URL", options.url || options.data?.url);
2613
+ pushLine(lines, "State", options.data?.state);
2614
+ pushLine(lines, "Draft", options.data?.isDraft ?? options.draft);
2615
+ pushLine(lines, "Base", options.data?.baseRefName ?? options.base);
2616
+ pushLine(lines, "Head", options.data?.headRefName ?? options.head);
2617
+ pushLine(lines, "Author", formatAuthor(options.data?.author));
2618
+ pushLine(lines, "Created", options.data?.createdAt);
2619
+ pushLine(lines, "Labels", formatLabels(options.data?.labels));
2620
+
2621
+ const bodyText = normalizeText(options.data?.body);
2622
+ if (bodyText) {
2623
+ lines.push("");
2624
+ lines.push("## Body");
2625
+ lines.push("");
2626
+ lines.push(bodyText);
2627
+ }
2628
+
2629
+ return lines.join("\n").trim();
2630
+ }
2631
+
2445
2632
  async function executeSearchIssues(
2446
2633
  session: ToolSession,
2447
2634
  params: GithubInput,
@@ -127,7 +127,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
127
127
  const response = await this.completeImageRequest(
128
128
  model,
129
129
  {
130
- systemPrompt: prompt.render(inspectImageSystemPromptTemplate),
130
+ systemPrompt: [prompt.render(inspectImageSystemPromptTemplate)],
131
131
  messages: [
132
132
  {
133
133
  role: "user",
@@ -337,7 +337,7 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
337
337
  }
338
338
 
339
339
  if (truncation.nextOffset != null) {
340
- notice += `. Use sel=${truncation.nextOffset} to continue`;
340
+ notice += `. Use :${truncation.nextOffset} to continue`;
341
341
  }
342
342
 
343
343
  if (truncation.artifactId != null) {
@@ -5,6 +5,7 @@ import * as url from "node:url";
5
5
  import { isEnoent } from "@oh-my-pi/pi-utils";
6
6
 
7
7
  const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
8
+ const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+)?|raw)$/i;
8
9
  const NARROW_NO_BREAK_SPACE = "\u202F";
9
10
  const TOP_LEVEL_INTERNAL_URL_PREFIXES = [
10
11
  "agent://",
@@ -102,6 +103,16 @@ export function expandPath(filePath: string): string {
102
103
  return expandTilde(normalized);
103
104
  }
104
105
 
106
+ export function splitPathAndSel(rawPath: string): { path: string; sel?: string } {
107
+ const colon = rawPath.lastIndexOf(":");
108
+ if (colon <= 0) return { path: rawPath };
109
+
110
+ const candidate = rawPath.slice(colon + 1);
111
+ if (!FILE_LINE_RANGE_RE.test(candidate)) return { path: rawPath };
112
+
113
+ return { path: rawPath.slice(0, colon), sel: candidate };
114
+ }
115
+
105
116
  function assertNotInternalUrl(expanded: string, original: string): void {
106
117
  for (const prefix of TOP_LEVEL_INTERNAL_URL_PREFIXES) {
107
118
  if (expanded.startsWith(prefix)) {