@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.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 (117) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/package.json +7 -7
  3. package/src/autoresearch/prompt.md +1 -1
  4. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  5. package/src/config/model-registry.ts +67 -15
  6. package/src/config/prompt-templates.ts +5 -5
  7. package/src/config/settings-schema.ts +4 -4
  8. package/src/cursor.ts +3 -8
  9. package/src/discovery/helpers.ts +3 -3
  10. package/src/edit/diff.ts +50 -47
  11. package/src/edit/index.ts +86 -57
  12. package/src/edit/line-hash.ts +735 -19
  13. package/src/edit/modes/apply-patch.ts +0 -9
  14. package/src/edit/modes/atom.ts +658 -0
  15. package/src/edit/modes/chunk.ts +14 -24
  16. package/src/edit/modes/hashline.ts +188 -136
  17. package/src/edit/modes/patch.ts +5 -9
  18. package/src/edit/modes/replace.ts +6 -11
  19. package/src/edit/renderer.ts +14 -10
  20. package/src/edit/streaming.ts +50 -16
  21. package/src/exec/bash-executor.ts +2 -4
  22. package/src/export/html/template.generated.ts +1 -1
  23. package/src/export/html/template.js +4 -12
  24. package/src/extensibility/custom-tools/types.ts +2 -0
  25. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/lsp/index.ts +1 -1
  28. package/src/mcp/render.ts +1 -8
  29. package/src/modes/components/assistant-message.ts +4 -0
  30. package/src/modes/components/diff.ts +23 -14
  31. package/src/modes/components/footer.ts +21 -16
  32. package/src/modes/components/settings-defs.ts +6 -1
  33. package/src/modes/components/todo-reminder.ts +1 -8
  34. package/src/modes/components/tool-execution.ts +1 -4
  35. package/src/modes/controllers/selector-controller.ts +1 -1
  36. package/src/modes/print-mode.ts +8 -0
  37. package/src/prompts/agents/librarian.md +1 -1
  38. package/src/prompts/agents/reviewer.md +4 -4
  39. package/src/prompts/ci-green-request.md +1 -1
  40. package/src/prompts/review-request.md +1 -1
  41. package/src/prompts/system/subagent-system-prompt.md +3 -3
  42. package/src/prompts/system/subagent-yield-reminder.md +11 -0
  43. package/src/prompts/system/system-prompt.md +3 -0
  44. package/src/prompts/tools/ask.md +3 -2
  45. package/src/prompts/tools/ast-edit.md +15 -19
  46. package/src/prompts/tools/ast-grep.md +18 -24
  47. package/src/prompts/tools/atom.md +96 -0
  48. package/src/prompts/tools/chunk-edit.md +37 -161
  49. package/src/prompts/tools/debug.md +4 -5
  50. package/src/prompts/tools/exit-plan-mode.md +4 -5
  51. package/src/prompts/tools/find.md +4 -8
  52. package/src/prompts/tools/github.md +18 -0
  53. package/src/prompts/tools/grep.md +4 -5
  54. package/src/prompts/tools/hashline.md +22 -89
  55. package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
  56. package/src/prompts/tools/inspect-image.md +6 -6
  57. package/src/prompts/tools/lsp.md +1 -1
  58. package/src/prompts/tools/patch.md +12 -19
  59. package/src/prompts/tools/python.md +3 -2
  60. package/src/prompts/tools/read-chunk.md +2 -3
  61. package/src/prompts/tools/read.md +2 -2
  62. package/src/prompts/tools/ssh.md +8 -17
  63. package/src/prompts/tools/todo-write.md +54 -41
  64. package/src/sdk.ts +14 -9
  65. package/src/session/agent-session.ts +25 -2
  66. package/src/task/executor.ts +43 -48
  67. package/src/task/render.ts +11 -13
  68. package/src/tools/ask.ts +7 -7
  69. package/src/tools/ast-edit.ts +45 -41
  70. package/src/tools/ast-grep.ts +77 -85
  71. package/src/tools/bash.ts +8 -9
  72. package/src/tools/browser.ts +32 -30
  73. package/src/tools/calculator.ts +4 -4
  74. package/src/tools/cancel-job.ts +1 -1
  75. package/src/tools/checkpoint.ts +2 -2
  76. package/src/tools/debug.ts +41 -37
  77. package/src/tools/exit-plan-mode.ts +1 -1
  78. package/src/tools/find.ts +4 -4
  79. package/src/tools/gh-renderer.ts +12 -4
  80. package/src/tools/gh.ts +509 -697
  81. package/src/tools/grep.ts +115 -130
  82. package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
  83. package/src/tools/index.ts +14 -32
  84. package/src/tools/inspect-image.ts +3 -3
  85. package/src/tools/json-tree.ts +114 -114
  86. package/src/tools/match-line-format.ts +9 -8
  87. package/src/tools/notebook.ts +8 -7
  88. package/src/tools/poll-tool.ts +2 -1
  89. package/src/tools/python.ts +9 -23
  90. package/src/tools/read.ts +32 -21
  91. package/src/tools/render-mermaid.ts +1 -1
  92. package/src/tools/render-utils.ts +18 -0
  93. package/src/tools/renderers.ts +2 -2
  94. package/src/tools/report-tool-issue.ts +3 -2
  95. package/src/tools/resolve.ts +1 -1
  96. package/src/tools/review.ts +12 -10
  97. package/src/tools/search-tool-bm25.ts +2 -4
  98. package/src/tools/ssh.ts +4 -4
  99. package/src/tools/todo-write.ts +172 -147
  100. package/src/tools/vim.ts +14 -15
  101. package/src/tools/write.ts +4 -4
  102. package/src/tools/{submit-result.ts → yield.ts} +11 -13
  103. package/src/utils/edit-mode.ts +2 -1
  104. package/src/utils/file-display-mode.ts +10 -5
  105. package/src/utils/git.ts +9 -5
  106. package/src/utils/shell-snapshot.ts +2 -3
  107. package/src/vim/render.ts +4 -4
  108. package/src/prompts/system/subagent-submit-reminder.md +0 -11
  109. package/src/prompts/tools/gh-issue-view.md +0 -11
  110. package/src/prompts/tools/gh-pr-checkout.md +0 -12
  111. package/src/prompts/tools/gh-pr-diff.md +0 -12
  112. package/src/prompts/tools/gh-pr-push.md +0 -12
  113. package/src/prompts/tools/gh-pr-view.md +0 -11
  114. package/src/prompts/tools/gh-repo-view.md +0 -11
  115. package/src/prompts/tools/gh-run-watch.md +0 -12
  116. package/src/prompts/tools/gh-search-issues.md +0 -11
  117. package/src/prompts/tools/gh-search-prs.md +0 -11
package/src/tools/read.ts CHANGED
@@ -8,7 +8,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
9
  import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
10
10
  import { type Static, Type } from "@sinclair/typebox";
11
- import { computeLineHash } from "../edit/line-hash";
11
+ import { formatHashLines } from "../edit/line-hash";
12
12
  import {
13
13
  type ChunkReadTarget,
14
14
  formatChunkedRead,
@@ -89,19 +89,11 @@ function isRemoteMountPath(absolutePath: string): boolean {
89
89
 
90
90
  function prependLineNumbers(text: string, startNum: number): string {
91
91
  const textLines = text.split("\n");
92
- const lastLineNum = startNum + textLines.length - 1;
93
- const padWidth = String(lastLineNum).length;
94
- return textLines
95
- .map((line, i) => {
96
- const lineNum = String(startNum + i).padStart(padWidth, " ");
97
- return `${lineNum}|${line}`;
98
- })
99
- .join("\n");
92
+ return textLines.map((line, i) => `${startNum + i}|${line}`).join("\n");
100
93
  }
101
94
 
102
95
  function prependHashLines(text: string, startNum: number): string {
103
- const textLines = text.split("\n");
104
- return textLines.map((line, i) => `${startNum + i}#${computeLineHash(startNum + i, line)}:${line}`).join("\n");
96
+ return formatHashLines(text, startNum);
105
97
  }
106
98
 
107
99
  function formatTextWithMode(
@@ -369,9 +361,9 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
369
361
  }
370
362
 
371
363
  const readSchema = Type.Object({
372
- path: Type.String({ description: "Path or URL to read" }),
373
- sel: Type.Optional(Type.String({ description: "Selector: chunk path, L10-L50, or raw" })),
374
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds", default: 20 })),
364
+ path: Type.String({ description: "path or url", examples: ["src/foo.ts", "https://example.com"] }),
365
+ sel: Type.Optional(Type.String({ description: "line range or mode", examples: ["L50", "L50-L120", "raw"] })),
366
+ timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 20 })),
375
367
  });
376
368
 
377
369
  export type ReadToolInput = Static<typeof readSchema>;
@@ -389,6 +381,10 @@ export interface ReadToolDetails {
389
381
  method?: string;
390
382
  notes?: string[];
391
383
  meta?: OutputMeta;
384
+ /** Raw text + start line for user-visible TUI rendering, set when content is text-like.
385
+ * Mirrors the same lines the model receives but without hashline/line-number prefixes,
386
+ * so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
387
+ displayContent?: { text: string; startLine: number };
392
388
  }
393
389
 
394
390
  type ReadParams = ReadToolInput;
@@ -599,9 +595,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
599
595
  sourceInternal?: string;
600
596
  entityLabel: string;
601
597
  ignoreResultLimits?: boolean;
598
+ raw?: boolean;
602
599
  },
603
600
  ): AgentToolResult<ReadToolDetails> {
604
- const displayMode = resolveFileDisplayMode(this.session);
601
+ const displayMode = resolveFileDisplayMode(this.session, { raw: options.raw });
605
602
  const details = options.details ?? {};
606
603
  const allLines = text.split("\n");
607
604
  const totalLines = allLines.length;
@@ -640,8 +637,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
640
637
 
641
638
  const shouldAddHashLines = displayMode.hashLines;
642
639
  const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
643
- const formatText = (content: string, startNum: number): string =>
644
- formatTextWithMode(content, startNum, shouldAddHashLines, shouldAddLineNumbers);
640
+ const formatText = (content: string, startNum: number): string => {
641
+ details.displayContent = { text: content, startLine: startNum };
642
+ return formatTextWithMode(content, startNum, shouldAddHashLines, shouldAddLineNumbers);
643
+ };
645
644
 
646
645
  let outputText: string;
647
646
  let truncationInfo:
@@ -743,6 +742,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
743
742
  limit: number | undefined,
744
743
  resolvedArchivePath: ResolvedArchiveReadPath,
745
744
  signal?: AbortSignal,
745
+ options?: { raw?: boolean },
746
746
  ): Promise<AgentToolResult<ReadToolDetails>> {
747
747
  throwIfAborted(signal);
748
748
  const archive = await openArchive(resolvedArchivePath.absolutePath);
@@ -787,6 +787,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
787
787
  details,
788
788
  sourcePath: resolvedArchivePath.absolutePath,
789
789
  entityLabel: "archive entry",
790
+ raw: options?.raw,
790
791
  });
791
792
  const firstText = result.content.find((content): content is TextContent => content.type === "text");
792
793
  if (firstText) {
@@ -978,7 +979,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
978
979
  const archivePath = await this.#resolveArchiveReadPath(localReadPath, signal);
979
980
  if (archivePath) {
980
981
  const { offset, limit } = selToOffsetLimit(parsed);
981
- return this.#readArchive(readPath, offset, limit, archivePath, signal);
982
+ return this.#readArchive(readPath, offset, limit, archivePath, signal, { raw: parsed.kind === "raw" });
982
983
  }
983
984
 
984
985
  const sqlitePath = await this.#resolveSqliteReadPath(readPath, signal);
@@ -1247,9 +1248,12 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1247
1248
  firstLineExceedsLimit,
1248
1249
  };
1249
1250
 
1250
- const shouldAddHashLines = displayMode.hashLines;
1251
- const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
1251
+ const isRawMode = parsed.kind === "raw";
1252
+ const shouldAddHashLines = !isRawMode && displayMode.hashLines;
1253
+ const shouldAddLineNumbers = isRawMode ? false : shouldAddHashLines ? false : displayMode.lineNumbers;
1254
+ let capturedDisplayContent: { text: string; startLine: number } | undefined;
1252
1255
  const formatText = (text: string, startNum: number): string => {
1256
+ capturedDisplayContent = { text, startLine: startNum };
1253
1257
  return formatTextWithMode(text, startNum, shouldAddHashLines, shouldAddLineNumbers);
1254
1258
  };
1255
1259
 
@@ -1300,6 +1304,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1300
1304
  sourcePath = absolutePath;
1301
1305
  }
1302
1306
 
1307
+ if (capturedDisplayContent) {
1308
+ details.displayContent = capturedDisplayContent;
1309
+ }
1310
+
1303
1311
  content = [{ type: "text", text: outputText }];
1304
1312
  }
1305
1313
 
@@ -1494,7 +1502,10 @@ export const readToolRenderer = {
1494
1502
  }
1495
1503
 
1496
1504
  const details = result.details;
1497
- const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
1505
+ const rawText = result.content?.find(c => c.type === "text")?.text ?? "";
1506
+ // Prefer structured `displayContent` from details when available so the TUI
1507
+ // shows clean file content (no model-only hashline anchors) without parsing the formatted text.
1508
+ const contentText = details?.displayContent?.text ?? rawText;
1498
1509
  const imageContent = result.content?.find(c => c.type === "image");
1499
1510
  const rawPath = args?.file_path || args?.path || "";
1500
1511
  const filePath = shortenPath(rawPath);
@@ -5,7 +5,7 @@ import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with {
5
5
  import type { ToolSession } from "./index";
6
6
 
7
7
  const renderMermaidSchema = Type.Object({
8
- mermaid: Type.String({ description: "Mermaid graph source text" }),
8
+ mermaid: Type.String({ description: "mermaid source", examples: ["graph TD; A-->B"] }),
9
9
  config: Type.Optional(
10
10
  Type.Object({
11
11
  useAscii: Type.Optional(Type.Boolean()),
@@ -182,6 +182,24 @@ export function formatEmptyMessage(message: string, theme: Theme): string {
182
182
  return `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`;
183
183
  }
184
184
 
185
+ // =============================================================================
186
+ // Code Frame Formatting
187
+ // =============================================================================
188
+
189
+ export type CodeFrameMarker = "" | " " | "*" | "+" | "-" | ">";
190
+
191
+ export function formatCodeFrameLine(
192
+ marker: CodeFrameMarker,
193
+ lineNumber: string | number,
194
+ content: string,
195
+ lineNumberWidth: number,
196
+ ): string {
197
+ const markerText = marker.trim();
198
+ const lineNumberText = String(lineNumber).trim();
199
+ const gutterText = markerText && lineNumberText ? `${markerText}${lineNumberText}` : lineNumberText || markerText;
200
+ return `${gutterText.padStart(lineNumberWidth + 1, " ")}│${content}`;
201
+ }
202
+
185
203
  // =============================================================================
186
204
  // Tool UI Helpers
187
205
  // =============================================================================
@@ -17,7 +17,7 @@ import { bashToolRenderer } from "./bash";
17
17
  import { calculatorToolRenderer } from "./calculator";
18
18
  import { debugToolRenderer } from "./debug";
19
19
  import { findToolRenderer } from "./find";
20
- import { ghRunWatchToolRenderer } from "./gh-renderer";
20
+ import { githubToolRenderer } from "./gh-renderer";
21
21
  import { grepToolRenderer } from "./grep";
22
22
  import { inspectImageToolRenderer } from "./inspect-image-renderer";
23
23
  import { notebookToolRenderer } from "./notebook";
@@ -63,7 +63,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
63
63
  ssh: sshToolRenderer as ToolRenderer,
64
64
  task: taskToolRenderer as ToolRenderer,
65
65
  todo_write: todoWriteToolRenderer as ToolRenderer,
66
- gh_run_watch: ghRunWatchToolRenderer as ToolRenderer,
66
+ github: githubToolRenderer as ToolRenderer,
67
67
  web_search: webSearchToolRenderer as ToolRenderer,
68
68
  write: writeToolRenderer as ToolRenderer,
69
69
  };
@@ -14,8 +14,8 @@ import type { Settings } from "..";
14
14
  import type { ToolSession } from "./index";
15
15
 
16
16
  const ReportToolIssueParams = Type.Object({
17
- tool: Type.String({ description: "Name of the tool that behaved unexpectedly" }),
18
- report: Type.String({ description: "Description of what was unexpected about the tool's behavior" }),
17
+ tool: Type.String({ description: "tool name", examples: ["bash", "read"] }),
18
+ report: Type.String({ description: "unexpected behavior" }),
19
19
  });
20
20
 
21
21
  export function isAutoQaEnabled(settings?: Settings): boolean {
@@ -57,6 +57,7 @@ export function createReportToolIssueTool(session: ToolSession): AgentTool {
57
57
  return {
58
58
  name: "report_tool_issue",
59
59
  label: "Report Tool Issue",
60
+ strict: false,
60
61
  description: "Report unexpected tool behavior for automated QA tracking.",
61
62
  parameters: ReportToolIssueParams,
62
63
  async execute(_toolCallId, rawParams) {
@@ -13,7 +13,7 @@ import { ToolError } from "./tool-errors";
13
13
 
14
14
  const resolveSchema = Type.Object({
15
15
  action: Type.Union([Type.Literal("apply"), Type.Literal("discard")]),
16
- reason: Type.String({ description: "Why you're applying or discarding" }),
16
+ reason: Type.String({ description: "reason for action", examples: ["approved by user"] }),
17
17
  });
18
18
 
19
19
  type ResolveParams = Static<typeof resolveSchema>;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Used by the reviewer agent to report findings in a structured way.
5
5
  * Hidden by default - only enabled when explicitly listed in agent's tools.
6
- * Reviewers finish via `submit_result` tool with SubmitReviewDetails schema.
6
+ * Reviewers finish via `yield` tool with SubmitReviewDetails schema.
7
7
  */
8
8
  // ─────────────────────────────────────────────────────────────────────────────
9
9
  // Subprocess tool handlers - registered for extraction/rendering in task tool
@@ -55,22 +55,24 @@ function getPriorityDisplay(
55
55
  // report_finding schema
56
56
  const ReportFindingParams = Type.Object({
57
57
  title: Type.String({
58
- description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'",
58
+ description: "prefixed imperative title",
59
+ examples: ["[P1] un-padding wrong dimension"],
59
60
  }),
60
61
  body: Type.String({
61
- description: "Markdown explaining why this is a problem. One paragraph max.",
62
+ description: "problem explanation",
62
63
  }),
63
64
  priority: StringEnum(["P0", "P1", "P2", "P3"], {
64
- description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
65
+ description: "priority 0-3",
65
66
  }),
66
67
  confidence: Type.Number({
67
68
  minimum: 0,
68
69
  maximum: 1,
69
- description: "Confidence score 0.0-1.0",
70
+ description: "confidence score",
71
+ examples: [0.0, 0.5, 1.0],
70
72
  }),
71
- file_path: Type.String({ description: "Absolute path to the file" }),
72
- line_start: Type.Number({ description: "Start line of the issue" }),
73
- line_end: Type.Number({ description: "End line of the issue" }),
73
+ file_path: Type.String({ description: "file path" }),
74
+ line_start: Type.Number({ description: "start line" }),
75
+ line_end: Type.Number({ description: "end line" }),
74
76
  });
75
77
 
76
78
  interface ReportFindingDetails {
@@ -131,7 +133,7 @@ export function parseReportFindingDetails(value: unknown): ReportFindingDetails
131
133
  export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
132
134
  name: "report_finding",
133
135
  label: "Report Finding",
134
- description: "Report a code review finding. Use this for each issue found. Call submit_result when done.",
136
+ description: "Report a code review finding. Use this for each issue found. Call yield when done.",
135
137
  parameters: ReportFindingParams,
136
138
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
137
139
  const { title, body, priority, confidence, file_path, line_start, line_end } = params;
@@ -186,7 +188,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
186
188
  },
187
189
  };
188
190
 
189
- /** SubmitReviewDetails - used for rendering review results from submit_result tool */
191
+ /** SubmitReviewDetails - used for rendering review results from yield tool */
190
192
  export interface SubmitReviewDetails {
191
193
  overall_correctness: "correct" | "incorrect";
192
194
  explanation: string;
@@ -25,10 +25,8 @@ const MATCH_LABEL_LEN = 72;
25
25
  const MATCH_DESCRIPTION_LEN = 96;
26
26
 
27
27
  const searchToolBm25Schema = Type.Object({
28
- query: Type.String({ description: "Search query for hidden MCP tool metadata" }),
29
- limit: Type.Optional(
30
- Type.Integer({ description: "Max matching tools to activate and return (default 8)", minimum: 1 }),
31
- ),
28
+ query: Type.String({ description: "mcp search query", examples: ["kubernetes pod", "image processing"] }),
29
+ limit: Type.Optional(Type.Integer({ description: "max matches", minimum: 1 })),
32
30
  });
33
31
 
34
32
  type SearchToolBm25Params = Static<typeof searchToolBm25Schema>;
package/src/tools/ssh.ts CHANGED
@@ -22,10 +22,10 @@ import { toolResult } from "./tool-result";
22
22
  import { clampTimeout } from "./tool-timeouts";
23
23
 
24
24
  const sshSchema = Type.Object({
25
- host: Type.String({ description: "Host name from managed SSH config or discovered ssh.json files" }),
26
- command: Type.String({ description: "Command to execute on the remote host" }),
27
- cwd: Type.Optional(Type.String({ description: "Remote working directory (optional)" })),
28
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds", default: 60 })),
25
+ host: Type.String({ description: "ssh host", examples: ["my-server", "prod-1"] }),
26
+ command: Type.String({ description: "remote command", examples: ["ls -la", "uptime"] }),
27
+ cwd: Type.Optional(Type.String({ description: "remote working directory", examples: ["/var/log"] })),
28
+ timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 60 })),
29
29
  });
30
30
 
31
31
  export interface SSHToolDetails {