@oh-my-pi/pi-coding-agent 14.5.3 → 14.5.5

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 (68) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/examples/extensions/plan-mode.ts +1 -1
  3. package/examples/sdk/README.md +1 -1
  4. package/package.json +7 -7
  5. package/src/config/prompt-templates.ts +103 -8
  6. package/src/config/settings-schema.ts +14 -13
  7. package/src/config/settings.ts +1 -1
  8. package/src/cursor.ts +4 -4
  9. package/src/edit/index.ts +111 -109
  10. package/src/edit/line-hash.ts +33 -3
  11. package/src/edit/modes/apply-patch.ts +6 -4
  12. package/src/edit/modes/atom.lark +27 -0
  13. package/src/edit/modes/atom.ts +1057 -841
  14. package/src/edit/modes/hashline.ts +9 -10
  15. package/src/edit/modes/patch.ts +23 -19
  16. package/src/edit/modes/replace.ts +19 -15
  17. package/src/edit/renderer.ts +65 -8
  18. package/src/edit/streaming.ts +47 -77
  19. package/src/extensibility/extensions/types.ts +11 -11
  20. package/src/extensibility/hooks/types.ts +6 -6
  21. package/src/lsp/edits.ts +8 -5
  22. package/src/lsp/index.ts +4 -4
  23. package/src/lsp/utils.ts +7 -7
  24. package/src/mcp/discoverable-tool-metadata.ts +1 -1
  25. package/src/mcp/manager.ts +3 -3
  26. package/src/mcp/tool-bridge.ts +4 -4
  27. package/src/memories/index.ts +1 -1
  28. package/src/modes/acp/acp-event-mapper.ts +1 -1
  29. package/src/modes/components/session-observer-overlay.ts +1 -1
  30. package/src/modes/components/settings-defs.ts +3 -3
  31. package/src/modes/components/tree-selector.ts +2 -2
  32. package/src/modes/utils/ui-helpers.ts +31 -7
  33. package/src/prompts/agents/explore.md +1 -1
  34. package/src/prompts/agents/librarian.md +2 -2
  35. package/src/prompts/agents/plan.md +2 -2
  36. package/src/prompts/agents/reviewer.md +1 -1
  37. package/src/prompts/agents/task.md +2 -2
  38. package/src/prompts/system/plan-mode-active.md +1 -1
  39. package/src/prompts/system/system-prompt.md +34 -31
  40. package/src/prompts/tools/apply-patch.md +0 -2
  41. package/src/prompts/tools/atom.md +81 -63
  42. package/src/prompts/tools/bash.md +7 -4
  43. package/src/prompts/tools/checkpoint.md +1 -1
  44. package/src/prompts/tools/find.md +6 -1
  45. package/src/prompts/tools/hashline.md +10 -11
  46. package/src/prompts/tools/patch.md +13 -13
  47. package/src/prompts/tools/read.md +4 -4
  48. package/src/prompts/tools/replace.md +3 -3
  49. package/src/prompts/tools/{grep.md → search.md} +4 -4
  50. package/src/sdk.ts +19 -9
  51. package/src/session/agent-session.ts +65 -0
  52. package/src/system-prompt.ts +15 -5
  53. package/src/task/executor.ts +5 -0
  54. package/src/task/index.ts +10 -1
  55. package/src/tools/ast-edit.ts +4 -6
  56. package/src/tools/ast-grep.ts +4 -6
  57. package/src/tools/bash.ts +1 -1
  58. package/src/tools/file-recorder.ts +6 -6
  59. package/src/tools/find.ts +11 -13
  60. package/src/tools/index.ts +7 -7
  61. package/src/tools/path-utils.ts +31 -4
  62. package/src/tools/read.ts +12 -6
  63. package/src/tools/renderers.ts +2 -2
  64. package/src/tools/{grep.ts → search.ts} +32 -40
  65. package/src/tools/write.ts +8 -4
  66. package/src/web/search/index.ts +1 -1
  67. package/src/edit/block.ts +0 -308
  68. package/src/edit/indent.ts +0 -150
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
5
5
  */
6
+
6
7
  import path from "node:path";
7
8
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
9
  import { logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
@@ -16,6 +17,7 @@ import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
16
17
  import type { CustomTool } from "../extensibility/custom-tools/types";
17
18
  import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
18
19
  import type { Skill } from "../extensibility/skills";
20
+ import type { LocalProtocolOptions } from "../internal-urls";
19
21
  import { callTool } from "../mcp/client";
20
22
  import type { MCPManager } from "../mcp/manager";
21
23
  import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
@@ -159,6 +161,8 @@ export interface ExecutorOptions {
159
161
  authStorage?: AuthStorage;
160
162
  modelRegistry?: ModelRegistry;
161
163
  settings?: Settings;
164
+ /** Override local:// protocol options so subagent shares parent's local:// root */
165
+ localProtocolOptions?: LocalProtocolOptions;
162
166
  }
163
167
 
164
168
  function parseStringifiedJson(value: unknown): unknown {
@@ -987,6 +991,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
987
991
  enableMCP,
988
992
  mcpManager: options.mcpManager,
989
993
  customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
994
+ localProtocolOptions: options.localProtocolOptions,
990
995
  });
991
996
 
992
997
  activeSession = session;
package/src/task/index.ts CHANGED
@@ -28,6 +28,7 @@ import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type:
28
28
  import { formatBytes, formatDuration } from "../tools/render-utils";
29
29
  // Import review tools for side effects (registers subagent tool handlers)
30
30
  import "../tools/review";
31
+ import type { LocalProtocolOptions } from "../internal-urls";
31
32
  import { generateCommitMessage } from "../utils/commit-message-generator";
32
33
  import * as git from "../utils/git";
33
34
  import { discoverAgents, getAgent } from "./discovery";
@@ -567,7 +568,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
567
568
  }
568
569
 
569
570
  const planModeState = this.session.getPlanModeState?.();
570
- const planModeTools = ["read", "grep", "find", "ls", "lsp", "web_search"];
571
+ const planModeTools = ["read", "search", "find", "lsp", "web_search"];
571
572
  const effectiveAgent: typeof agent = planModeState?.enabled
572
573
  ? {
573
574
  ...agent,
@@ -715,6 +716,12 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
715
716
  const tempArtifactsDir = artifactsDir ? null : path.join(os.tmpdir(), `omp-task-${Snowflake.next()}`);
716
717
  const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
717
718
 
719
+ // Share the parent session's local:// root with subagents so they read/write the same scratch space
720
+ const localProtocolOptions: LocalProtocolOptions = {
721
+ getArtifactsDir: this.session.getArtifactsDir ?? (() => null),
722
+ getSessionId: this.session.getSessionId ?? (() => null),
723
+ };
724
+
718
725
  // Initialize progress tracking
719
726
  const progressMap = new Map<number, AgentProgress>();
720
727
 
@@ -856,6 +863,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
856
863
  contextFiles,
857
864
  skills: availableSkills,
858
865
  promptTemplates,
866
+ localProtocolOptions,
859
867
  });
860
868
  }
861
869
 
@@ -909,6 +917,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
909
917
  contextFiles,
910
918
  skills: availableSkills,
911
919
  promptTemplates,
920
+ localProtocolOptions,
912
921
  });
913
922
  if (mergeMode === "branch" && result.exitCode === 0) {
914
923
  try {
@@ -1,4 +1,3 @@
1
- import * as path from "node:path";
2
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
2
  import { type AstReplaceChange, astEdit } from "@oh-my-pi/pi-natives";
4
3
  import type { Component } from "@oh-my-pi/pi-tui";
@@ -16,6 +15,7 @@ import { createFileRecorder, formatResultPath } from "./file-recorder";
16
15
  import { formatGroupedFiles } from "./grouped-file-output";
17
16
  import type { OutputMeta } from "./output-meta";
18
17
  import {
18
+ formatPathRelativeToCwd,
19
19
  hasGlobPathChars,
20
20
  normalizePathLikeInput,
21
21
  parseSearchPath,
@@ -106,10 +106,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
106
106
  const normalizedRewrites = Object.fromEntries(ops);
107
107
  const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
108
108
 
109
- const formatScopePath = (targetPath: string): string => {
110
- const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
111
- return relative.length === 0 ? "." : relative;
112
- };
109
+ const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
113
110
  let searchPath: string | undefined;
114
111
  let scopePath: string | undefined;
115
112
  let globFilter: string | undefined;
@@ -164,7 +161,8 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
164
161
  });
165
162
 
166
163
  const dedupedParseErrors = dedupeParseErrors(result.parseErrors);
167
- const formatPath = (filePath: string): string => formatResultPath(filePath, isDirectory);
164
+ const formatPath = (filePath: string): string =>
165
+ formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
168
166
 
169
167
  const { record: recordFile, list: fileList } = createFileRecorder();
170
168
  const fileReplacementCounts = new Map<string, number>();
@@ -1,4 +1,3 @@
1
- import * as path from "node:path";
2
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
2
  import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
4
3
  import type { Component } from "@oh-my-pi/pi-tui";
@@ -16,6 +15,7 @@ import { formatGroupedFiles } from "./grouped-file-output";
16
15
  import { formatMatchLine } from "./match-line-format";
17
16
  import type { OutputMeta } from "./output-meta";
18
17
  import {
18
+ formatPathRelativeToCwd,
19
19
  hasGlobPathChars,
20
20
  normalizePathLikeInput,
21
21
  parseSearchPath,
@@ -87,10 +87,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
87
87
  if (!Number.isFinite(skip) || skip < 0) {
88
88
  throw new ToolError("skip must be a non-negative number");
89
89
  }
90
- const formatScopePath = (targetPath: string): string => {
91
- const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
92
- return relative.length === 0 ? "." : relative;
93
- };
90
+ const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
94
91
  let searchPath: string | undefined;
95
92
  let scopePath: string | undefined;
96
93
  let globFilter: string | undefined;
@@ -147,7 +144,8 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
147
144
  return parseError?.[1] ?? error;
148
145
  });
149
146
  const dedupedParseErrors = dedupeParseErrors(normalizedParseErrors);
150
- const formatPath = (filePath: string): string => formatResultPath(filePath, isDirectory);
147
+ const formatPath = (filePath: string): string =>
148
+ formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
151
149
 
152
150
  const { record: recordFile, list: fileList } = createFileRecorder();
153
151
  const fileMatchCounts = new Map<string, number>();
package/src/tools/bash.ts CHANGED
@@ -269,7 +269,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
269
269
  autoBackgroundThresholdSeconds: Math.max(0, Math.floor(this.#autoBackgroundThresholdMs / 1000)),
270
270
  hasAstGrep: this.session.settings.get("astGrep.enabled"),
271
271
  hasAstEdit: this.session.settings.get("astEdit.enabled"),
272
- hasGrep: this.session.settings.get("grep.enabled"),
272
+ hasSearch: this.session.settings.get("search.enabled"),
273
273
  hasFind: this.session.settings.get("find.enabled"),
274
274
  });
275
275
  }
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { formatPathRelativeToCwd } from "./path-utils";
2
3
 
3
4
  /**
4
5
  * Creates a deduplicating recorder for relative file paths.
@@ -22,14 +23,13 @@ export function createFileRecorder(): {
22
23
  }
23
24
 
24
25
  /**
25
- * Strip a leading slash and, when the search scope is a directory, normalize
26
- * Windows-style separators. For single-file scopes, fall back to the basename
27
- * so tool output does not leak absolute paths.
26
+ * Strip native virtual-root prefixes and format file paths relative to cwd when
27
+ * they are inside cwd. Paths outside cwd remain absolute.
28
28
  */
29
- export function formatResultPath(filePath: string, isDirectory: boolean): string {
29
+ export function formatResultPath(filePath: string, isDirectory: boolean, basePath: string, cwd: string): string {
30
30
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
31
31
  if (isDirectory) {
32
- return cleanPath.replace(/\\/g, "/");
32
+ return formatPathRelativeToCwd(path.resolve(basePath, cleanPath), cwd);
33
33
  }
34
- return path.basename(cleanPath);
34
+ return formatPathRelativeToCwd(basePath, cwd);
35
35
  }
package/src/tools/find.ts CHANGED
@@ -23,7 +23,13 @@ import {
23
23
  import type { ToolSession } from ".";
24
24
  import { applyListLimit } from "./list-limit";
25
25
  import { formatFullOutputReference, type OutputMeta } from "./output-meta";
26
- import { normalizePathLikeInput, parseFindPattern, resolveMultiFindPattern, resolveToCwd } from "./path-utils";
26
+ import {
27
+ formatPathRelativeToCwd,
28
+ normalizePathLikeInput,
29
+ parseFindPattern,
30
+ resolveMultiFindPattern,
31
+ resolveToCwd,
32
+ } from "./path-utils";
27
33
  import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils";
28
34
  import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
29
35
  import { toolResult } from "./tool-result";
@@ -101,10 +107,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
101
107
  const { pattern, limit, hidden } = params;
102
108
 
103
109
  return untilAborted(signal, async () => {
104
- const formatScopePath = (targetPath: string): string => {
105
- const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
106
- return relative.length === 0 ? "." : relative;
107
- };
110
+ const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
108
111
  const normalizedPattern = normalizePathLikeInput(pattern).replace(/\\/g, "/");
109
112
  if (!normalizedPattern) {
110
113
  throw new ToolError("Pattern must not be empty");
@@ -132,14 +135,9 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
132
135
  const formatMatchPath = (matchPath: string, fileType?: natives.FileType): string => {
133
136
  const hadTrailingSlash = matchPath.endsWith("/") || matchPath.endsWith("\\");
134
137
  const absolutePath = path.isAbsolute(matchPath) ? matchPath : path.resolve(searchPath, matchPath);
135
- let relativePath = path.relative(this.session.cwd, absolutePath).replace(/\\/g, "/");
136
- if (relativePath.length === 0) {
137
- relativePath = ".";
138
- }
139
- if ((fileType === natives.FileType.Dir || hadTrailingSlash) && !relativePath.endsWith("/")) {
140
- relativePath += "/";
141
- }
142
- return relativePath;
138
+ return formatPathRelativeToCwd(absolutePath, this.session.cwd, {
139
+ trailingSlash: fileType === natives.FileType.Dir || hadTrailingSlash,
140
+ });
143
141
  };
144
142
 
145
143
  const buildResult = (files: string[]): AgentToolResult<FindToolDetails> => {
@@ -18,7 +18,7 @@ import type { ToolChoiceQueue } from "../session/tool-choice-queue";
18
18
  import { TaskTool } from "../task";
19
19
  import type { AgentOutputManager } from "../task/output-manager";
20
20
  import type { EventBus } from "../utils/event-bus";
21
- import { SearchTool } from "../web/search";
21
+ import { WebSearchTool } from "../web/search";
22
22
  import { AskTool } from "./ask";
23
23
  import { AstEditTool } from "./ast-edit";
24
24
  import { AstGrepTool } from "./ast-grep";
@@ -30,7 +30,6 @@ import { DebugTool } from "./debug";
30
30
  import { ExitPlanModeTool } from "./exit-plan-mode";
31
31
  import { FindTool } from "./find";
32
32
  import { GithubTool } from "./gh";
33
- import { GrepTool } from "./grep";
34
33
  import { InspectImageTool } from "./inspect-image";
35
34
  import { IrcTool } from "./irc";
36
35
  import { JobTool } from "./job";
@@ -42,6 +41,7 @@ import { RenderMermaidTool } from "./render-mermaid";
42
41
  import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
43
42
  import { ResolveTool } from "./resolve";
44
43
  import { reportFindingTool } from "./review";
44
+ import { SearchTool } from "./search";
45
45
  import { SearchToolBm25Tool } from "./search-tool-bm25";
46
46
  import { loadSshTool } from "./ssh";
47
47
  import { type TodoPhase, TodoWriteTool } from "./todo-write";
@@ -68,7 +68,6 @@ export * from "./debug";
68
68
  export * from "./exit-plan-mode";
69
69
  export * from "./find";
70
70
  export * from "./gh";
71
- export * from "./grep";
72
71
  export * from "./image-gen";
73
72
  export * from "./inspect-image";
74
73
  export * from "./irc";
@@ -80,6 +79,7 @@ export * from "./render-mermaid";
80
79
  export * from "./report-tool-issue";
81
80
  export * from "./resolve";
82
81
  export * from "./review";
82
+ export * from "./search";
83
83
  export * from "./search-tool-bm25";
84
84
  export * from "./ssh";
85
85
  export * from "./todo-write";
@@ -214,7 +214,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
214
214
  edit: s => new EditTool(s),
215
215
  github: GithubTool.createIf,
216
216
  find: s => new FindTool(s),
217
- grep: s => new GrepTool(s),
217
+ search: s => new SearchTool(s),
218
218
  lsp: LspTool.createIf,
219
219
  notebook: s => new NotebookTool(s),
220
220
  read: s => new ReadTool(s),
@@ -226,7 +226,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
226
226
  job: JobTool.createIf,
227
227
  irc: IrcTool.createIf,
228
228
  todo_write: s => new TodoWriteTool(s),
229
- web_search: s => new SearchTool(s),
229
+ web_search: s => new WebSearchTool(s),
230
230
  search_tool_bm25: SearchToolBm25Tool.createIf,
231
231
  write: s => new WriteTool(s),
232
232
  };
@@ -357,7 +357,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
357
357
  // Auto-include AST counterparts when their text-based sibling is present
358
358
  if (requestedTools) {
359
359
  if (
360
- requestedTools.includes("grep") &&
360
+ requestedTools.includes("search") &&
361
361
  !requestedTools.includes("ast_grep") &&
362
362
  session.settings.get("astGrep.enabled")
363
363
  ) {
@@ -379,7 +379,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
379
379
  if (name === "debug") return session.settings.get("debug.enabled");
380
380
  if (name === "todo_write") return !includeYield && session.settings.get("todo.enabled");
381
381
  if (name === "find") return session.settings.get("find.enabled");
382
- if (name === "grep") return session.settings.get("grep.enabled");
382
+ if (name === "search") return session.settings.get("search.enabled");
383
383
  if (name === "github") return session.settings.get("github.enabled");
384
384
  if (name === "ast_grep") return session.settings.get("astGrep.enabled");
385
385
  if (name === "ast_edit") return session.settings.get("astEdit.enabled");
@@ -157,6 +157,27 @@ export function resolveToCwd(filePath: string, cwd: string): string {
157
157
  return path.resolve(cwd, expanded);
158
158
  }
159
159
 
160
+ export function formatPathRelativeToCwd(
161
+ filePath: string,
162
+ cwd: string,
163
+ options: { trailingSlash?: boolean } = {},
164
+ ): string {
165
+ const resolvedCwd = path.resolve(cwd);
166
+ const normalized = normalizeLocalScheme(filePath);
167
+ if (isInternalUrlPath(normalized)) {
168
+ return normalized;
169
+ }
170
+ const expanded = expandPath(normalized);
171
+ const resolvedPath = path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(cwd, expanded);
172
+ const relative = path.relative(resolvedCwd, resolvedPath);
173
+ const isWithinCwd = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
174
+ let displayPath = normalizePosixPath(isWithinCwd ? relative || "." : resolvedPath);
175
+ if (options.trailingSlash && displayPath !== "." && !displayPath.endsWith("/")) {
176
+ displayPath += "/";
177
+ }
178
+ return displayPath;
179
+ }
180
+
160
181
  /**
161
182
  * Strip matching surrounding double quotes from a path string.
162
183
  * Common when users paste quoted paths from Windows Explorer or shell copy-paste.
@@ -381,8 +402,14 @@ function findCommonBasePath(paths: string[]): string {
381
402
  return joined || path.parse(path.resolve(paths[0])).root;
382
403
  }
383
404
 
384
- function toScopeDisplay(items: string[]): string {
385
- return items.map(item => normalizePosixPath(item)).join(", ");
405
+ function toScopeDisplay(items: string[], cwd: string): string {
406
+ return items
407
+ .map(item =>
408
+ formatPathRelativeToCwd(item, cwd, {
409
+ trailingSlash: item.endsWith("/") || item.endsWith("\\"),
410
+ }),
411
+ )
412
+ .join(", ");
386
413
  }
387
414
 
388
415
  function looksLikeDelimitedPathToken(token: string): boolean {
@@ -533,7 +560,7 @@ export async function resolveMultiSearchPath(
533
560
  return {
534
561
  basePath: commonBasePath,
535
562
  glob: buildBraceUnion(combinedPatterns),
536
- scopePath: toScopeDisplay(pathItems),
563
+ scopePath: toScopeDisplay(pathItems, cwd),
537
564
  exactFilePaths: allExactFiles ? parsedItems.map(item => item.absoluteBasePath) : undefined,
538
565
  };
539
566
  }
@@ -571,7 +598,7 @@ export async function resolveMultiFindPattern(
571
598
  return {
572
599
  basePath: commonBasePath,
573
600
  globPattern: buildBraceUnion(combinedPatterns) ?? "**/*",
574
- scopePath: toScopeDisplay(patternItems),
601
+ scopePath: toScopeDisplay(patternItems, cwd),
575
602
  };
576
603
  }
577
604
 
package/src/tools/read.ts CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  } from "./fetch";
41
41
  import { applyListLimit } from "./list-limit";
42
42
  import { formatFullOutputReference, formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
43
- import { expandPath, resolveReadPath } from "./path-utils";
43
+ import { expandPath, formatPathRelativeToCwd, resolveReadPath } from "./path-utils";
44
44
  import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-utils";
45
45
  import {
46
46
  executeReadQuery,
@@ -1046,7 +1046,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1046
1046
  ? "- Alpha: no"
1047
1047
  : "- Alpha: unknown",
1048
1048
  "",
1049
- `If you want to analyze the image, call inspect_image with path="${readPath}" and a question describing what to inspect and the desired output format.`,
1049
+ `If you want to analyze the image, call inspect_image with path="${formatPathRelativeToCwd(
1050
+ absolutePath,
1051
+ this.session.cwd,
1052
+ )}" and a question describing what to inspect and the desired output format.`,
1050
1053
  ];
1051
1054
  content = [{ type: "text", text: metadataLines.join("\n") }];
1052
1055
  details = {};
@@ -1110,12 +1113,15 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1110
1113
  const effectiveLimit = limit ?? DEFAULT_LIMIT;
1111
1114
  const maxLinesToCollect = Math.min(effectiveLimit, DEFAULT_MAX_LINES);
1112
1115
  const selectedLineLimit = effectiveLimit;
1116
+ // Scale byte budget with line limit so the configured line count actually fits.
1117
+ // Assume ~512 bytes/line average; never go below the shared default.
1118
+ const maxBytesForRead = Math.max(DEFAULT_MAX_BYTES, maxLinesToCollect * 512);
1113
1119
 
1114
1120
  const streamResult = await streamLinesFromFile(
1115
1121
  absolutePath,
1116
1122
  startLine,
1117
1123
  maxLinesToCollect,
1118
- DEFAULT_MAX_BYTES,
1124
+ maxBytesForRead,
1119
1125
  selectedLineLimit,
1120
1126
  signal,
1121
1127
  );
@@ -1146,7 +1152,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1146
1152
  const totalSelectedLines = totalFileLines - startLine;
1147
1153
  const totalSelectedBytes = collectedBytes;
1148
1154
  const wasTruncated = collectedLines.length < totalSelectedLines || stoppedByByteLimit;
1149
- const firstLineExceedsLimit = firstLineByteLength !== undefined && firstLineByteLength > DEFAULT_MAX_BYTES;
1155
+ const firstLineExceedsLimit = firstLineByteLength !== undefined && firstLineByteLength > maxBytesForRead;
1150
1156
 
1151
1157
  const truncation: TruncationResult = {
1152
1158
  content: selectedContent,
@@ -1178,14 +1184,14 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1178
1184
  if (shouldAddHashLines) {
1179
1185
  outputText = `[Line ${startLineDisplay} is ${formatBytes(
1180
1186
  firstLineBytes,
1181
- )}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Hashline output requires full lines; cannot compute hashes for a truncated preview.]`;
1187
+ )}, exceeds ${formatBytes(maxBytesForRead)} limit. Hashline output requires full lines; cannot compute hashes for a truncated preview.]`;
1182
1188
  } else {
1183
1189
  outputText = formatText(snippet.text, startLineDisplay);
1184
1190
  }
1185
1191
  if (snippet.text.length === 0) {
1186
1192
  outputText = `[Line ${startLineDisplay} is ${formatBytes(
1187
1193
  firstLineBytes,
1188
- )}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
1194
+ )}, exceeds ${formatBytes(maxBytesForRead)} limit. Unable to display a valid UTF-8 snippet.]`;
1189
1195
  }
1190
1196
  details = { truncation };
1191
1197
  sourcePath = absolutePath;
@@ -18,13 +18,13 @@ import { calculatorToolRenderer } from "./calculator";
18
18
  import { debugToolRenderer } from "./debug";
19
19
  import { findToolRenderer } from "./find";
20
20
  import { githubToolRenderer } from "./gh-renderer";
21
- import { grepToolRenderer } from "./grep";
22
21
  import { inspectImageToolRenderer } from "./inspect-image-renderer";
23
22
  import { jobToolRenderer } from "./job";
24
23
  import { notebookToolRenderer } from "./notebook";
25
24
  import { pythonToolRenderer } from "./python";
26
25
  import { readToolRenderer } from "./read";
27
26
  import { resolveToolRenderer } from "./resolve";
27
+ import { searchToolRenderer } from "./search";
28
28
  import { searchToolBm25Renderer } from "./search-tool-bm25";
29
29
  import { sshToolRenderer } from "./ssh";
30
30
  import { todoWriteToolRenderer } from "./todo-write";
@@ -54,7 +54,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
54
54
  edit: editToolRenderer as ToolRenderer,
55
55
  apply_patch: editToolRenderer as ToolRenderer,
56
56
  find: findToolRenderer as ToolRenderer,
57
- grep: grepToolRenderer as ToolRenderer,
57
+ search: searchToolRenderer as ToolRenderer,
58
58
  lsp: lspToolRenderer as ToolRenderer,
59
59
  notebook: notebookToolRenderer as ToolRenderer,
60
60
  inspect_image: inspectImageToolRenderer as ToolRenderer,