@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2

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 (235) hide show
  1. package/CHANGELOG.md +316 -1
  2. package/package.json +86 -24
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +116 -30
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +123 -178
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -8
  28. package/src/commit/agentic/index.ts +22 -26
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  31. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  32. package/src/commit/agentic/tools/git-overview.ts +6 -9
  33. package/src/commit/agentic/tools/index.ts +6 -8
  34. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  35. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  36. package/src/commit/agentic/tools/split-commit.ts +4 -4
  37. package/src/commit/agentic/validation.ts +1 -1
  38. package/src/commit/analysis/conventional.ts +4 -4
  39. package/src/commit/analysis/summary.ts +3 -3
  40. package/src/commit/changelog/generate.ts +4 -4
  41. package/src/commit/changelog/index.ts +5 -9
  42. package/src/commit/map-reduce/map-phase.ts +4 -4
  43. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  44. package/src/commit/pipeline.ts +13 -16
  45. package/src/config/keybindings.ts +7 -6
  46. package/src/config/prompt-templates.ts +44 -226
  47. package/src/config/resolve-config-value.ts +4 -2
  48. package/src/config/settings-schema.ts +98 -2
  49. package/src/config/settings.ts +25 -26
  50. package/src/dap/client.ts +674 -0
  51. package/src/dap/config.ts +150 -0
  52. package/src/dap/defaults.json +211 -0
  53. package/src/dap/index.ts +4 -0
  54. package/src/dap/session.ts +1255 -0
  55. package/src/dap/types.ts +600 -0
  56. package/src/debug/log-viewer.ts +3 -2
  57. package/src/discovery/builtin.ts +1 -2
  58. package/src/discovery/codex.ts +2 -2
  59. package/src/discovery/github.ts +2 -1
  60. package/src/discovery/helpers.ts +2 -2
  61. package/src/discovery/opencode.ts +2 -2
  62. package/src/edit/diff.ts +818 -0
  63. package/src/edit/index.ts +309 -0
  64. package/src/edit/line-hash.ts +67 -0
  65. package/src/edit/modes/chunk.ts +454 -0
  66. package/src/{patch → edit/modes}/hashline.ts +741 -361
  67. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  68. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  69. package/src/{patch → edit}/normalize.ts +97 -76
  70. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  71. package/src/exec/bash-executor.ts +4 -2
  72. package/src/exec/idle-timeout-watchdog.ts +126 -0
  73. package/src/exec/non-interactive-env.ts +5 -0
  74. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
  75. package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
  76. package/src/extensibility/custom-commands/loader.ts +1 -2
  77. package/src/extensibility/custom-tools/loader.ts +34 -11
  78. package/src/extensibility/custom-tools/types.ts +1 -1
  79. package/src/extensibility/extensions/loader.ts +9 -4
  80. package/src/extensibility/extensions/runner.ts +24 -1
  81. package/src/extensibility/extensions/types.ts +4 -2
  82. package/src/extensibility/hooks/loader.ts +5 -6
  83. package/src/extensibility/hooks/types.ts +2 -2
  84. package/src/extensibility/plugins/doctor.ts +2 -1
  85. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  86. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  87. package/src/extensibility/slash-commands.ts +3 -7
  88. package/src/index.ts +3 -1
  89. package/src/internal-urls/docs-index.generated.ts +11 -11
  90. package/src/ipy/executor.ts +58 -17
  91. package/src/ipy/gateway-coordinator.ts +6 -4
  92. package/src/ipy/kernel.ts +45 -22
  93. package/src/ipy/runtime.ts +2 -2
  94. package/src/lsp/client.ts +7 -4
  95. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  96. package/src/lsp/config.ts +2 -2
  97. package/src/lsp/defaults.json +688 -154
  98. package/src/lsp/index.ts +234 -45
  99. package/src/lsp/lspmux.ts +2 -2
  100. package/src/lsp/startup-events.ts +13 -0
  101. package/src/lsp/types.ts +12 -1
  102. package/src/lsp/utils.ts +8 -1
  103. package/src/main.ts +125 -47
  104. package/src/memories/index.ts +4 -5
  105. package/src/modes/acp/acp-agent.ts +563 -163
  106. package/src/modes/acp/acp-event-mapper.ts +9 -1
  107. package/src/modes/acp/acp-mode.ts +4 -2
  108. package/src/modes/components/agent-dashboard.ts +3 -4
  109. package/src/modes/components/diff.ts +6 -7
  110. package/src/modes/components/footer.ts +9 -29
  111. package/src/modes/components/hook-editor.ts +3 -3
  112. package/src/modes/components/hook-selector.ts +6 -1
  113. package/src/modes/components/read-tool-group.ts +6 -12
  114. package/src/modes/components/session-observer-overlay.ts +472 -0
  115. package/src/modes/components/settings-defs.ts +24 -0
  116. package/src/modes/components/status-line.ts +15 -61
  117. package/src/modes/components/tool-execution.ts +1 -1
  118. package/src/modes/components/welcome.ts +1 -1
  119. package/src/modes/controllers/btw-controller.ts +2 -2
  120. package/src/modes/controllers/command-controller.ts +4 -2
  121. package/src/modes/controllers/event-controller.ts +59 -2
  122. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  123. package/src/modes/controllers/input-controller.ts +15 -8
  124. package/src/modes/controllers/selector-controller.ts +26 -0
  125. package/src/modes/index.ts +20 -2
  126. package/src/modes/interactive-mode.ts +278 -69
  127. package/src/modes/rpc/host-tools.ts +186 -0
  128. package/src/modes/rpc/rpc-client.ts +178 -13
  129. package/src/modes/rpc/rpc-mode.ts +73 -3
  130. package/src/modes/rpc/rpc-types.ts +53 -1
  131. package/src/modes/session-observer-registry.ts +146 -0
  132. package/src/modes/shared.ts +0 -42
  133. package/src/modes/theme/theme.ts +80 -8
  134. package/src/modes/types.ts +4 -2
  135. package/src/modes/utils/keybinding-matchers.ts +9 -0
  136. package/src/prompts/system/custom-system-prompt.md +5 -0
  137. package/src/prompts/system/system-prompt.md +8 -1
  138. package/src/prompts/tools/chunk-edit.md +219 -0
  139. package/src/prompts/tools/debug.md +43 -0
  140. package/src/prompts/tools/grep.md +3 -0
  141. package/src/prompts/tools/lsp.md +5 -5
  142. package/src/prompts/tools/read-chunk.md +17 -0
  143. package/src/prompts/tools/read.md +19 -5
  144. package/src/sdk.ts +216 -165
  145. package/src/secrets/index.ts +1 -1
  146. package/src/secrets/obfuscator.ts +25 -17
  147. package/src/session/agent-session.ts +381 -286
  148. package/src/session/agent-storage.ts +12 -12
  149. package/src/session/compaction/branch-summarization.ts +3 -3
  150. package/src/session/compaction/compaction.ts +5 -6
  151. package/src/session/compaction/utils.ts +3 -3
  152. package/src/session/history-storage.ts +62 -19
  153. package/src/session/messages.ts +3 -3
  154. package/src/session/session-dump-format.ts +203 -0
  155. package/src/session/session-manager.ts +15 -5
  156. package/src/session/session-storage.ts +4 -2
  157. package/src/session/streaming-output.ts +1 -1
  158. package/src/session/tool-choice-queue.ts +213 -0
  159. package/src/slash-commands/builtin-registry.ts +56 -8
  160. package/src/ssh/connection-manager.ts +2 -2
  161. package/src/ssh/sshfs-mount.ts +5 -5
  162. package/src/stt/downloader.ts +4 -4
  163. package/src/stt/recorder.ts +4 -4
  164. package/src/stt/transcriber.ts +2 -2
  165. package/src/system-prompt.ts +25 -13
  166. package/src/task/agents.ts +5 -6
  167. package/src/task/commands.ts +2 -5
  168. package/src/task/executor.ts +32 -4
  169. package/src/task/index.ts +91 -82
  170. package/src/task/template.ts +2 -2
  171. package/src/task/types.ts +25 -0
  172. package/src/task/worktree.ts +131 -149
  173. package/src/tools/ask.ts +2 -3
  174. package/src/tools/ast-edit.ts +7 -7
  175. package/src/tools/ast-grep.ts +7 -7
  176. package/src/tools/auto-generated-guard.ts +36 -41
  177. package/src/tools/await-tool.ts +2 -2
  178. package/src/tools/bash.ts +5 -23
  179. package/src/tools/browser.ts +4 -5
  180. package/src/tools/calculator.ts +2 -3
  181. package/src/tools/cancel-job.ts +2 -2
  182. package/src/tools/checkpoint.ts +3 -3
  183. package/src/tools/debug.ts +1007 -0
  184. package/src/tools/exit-plan-mode.ts +3 -3
  185. package/src/tools/fetch.ts +67 -3
  186. package/src/tools/find.ts +4 -5
  187. package/src/tools/fs-cache-invalidation.ts +5 -0
  188. package/src/tools/gemini-image.ts +13 -5
  189. package/src/tools/gh.ts +130 -308
  190. package/src/tools/grep.ts +57 -9
  191. package/src/tools/index.ts +44 -22
  192. package/src/tools/inspect-image.ts +4 -4
  193. package/src/tools/output-meta.ts +1 -1
  194. package/src/tools/python.ts +19 -6
  195. package/src/tools/read.ts +211 -146
  196. package/src/tools/render-mermaid.ts +2 -3
  197. package/src/tools/render-utils.ts +20 -6
  198. package/src/tools/renderers.ts +3 -1
  199. package/src/tools/report-tool-issue.ts +80 -0
  200. package/src/tools/resolve.ts +70 -39
  201. package/src/tools/search-tool-bm25.ts +2 -2
  202. package/src/tools/ssh.ts +2 -2
  203. package/src/tools/todo-write.ts +2 -2
  204. package/src/tools/tool-timeouts.ts +1 -0
  205. package/src/tools/write.ts +5 -6
  206. package/src/tui/tree-list.ts +3 -1
  207. package/src/utils/clipboard.ts +80 -0
  208. package/src/utils/commit-message-generator.ts +2 -3
  209. package/src/utils/edit-mode.ts +49 -0
  210. package/src/utils/external-editor.ts +11 -5
  211. package/src/utils/file-display-mode.ts +6 -5
  212. package/src/utils/file-mentions.ts +8 -7
  213. package/src/utils/git.ts +1400 -0
  214. package/src/utils/image-loading.ts +98 -0
  215. package/src/utils/title-generator.ts +2 -3
  216. package/src/utils/tools-manager.ts +6 -6
  217. package/src/web/scrapers/choosealicense.ts +1 -1
  218. package/src/web/search/index.ts +3 -3
  219. package/src/web/search/render.ts +6 -4
  220. package/src/autoresearch/command-initialize.md +0 -34
  221. package/src/commit/git/errors.ts +0 -9
  222. package/src/commit/git/index.ts +0 -210
  223. package/src/commit/git/operations.ts +0 -54
  224. package/src/patch/diff.ts +0 -433
  225. package/src/patch/index.ts +0 -888
  226. package/src/patch/parser.ts +0 -532
  227. package/src/patch/types.ts +0 -292
  228. package/src/prompts/agents/oracle.md +0 -77
  229. package/src/tools/gh-cli.ts +0 -125
  230. package/src/tools/pending-action.ts +0 -49
  231. package/src/utils/child-process.ts +0 -88
  232. package/src/utils/frontmatter.ts +0 -117
  233. package/src/utils/image-input.ts +0 -274
  234. package/src/utils/mime.ts +0 -53
  235. package/src/utils/prompt-format.ts +0 -170
package/src/tools/read.ts CHANGED
@@ -5,15 +5,22 @@ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
5
5
  import { glob } from "@oh-my-pi/pi-natives";
6
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
7
  import { Text } from "@oh-my-pi/pi-tui";
8
- import { getRemoteDir, untilAborted } from "@oh-my-pi/pi-utils";
8
+ import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
9
9
  import { type Static, Type } from "@sinclair/typebox";
10
- import { renderPromptTemplate } from "../config/prompt-templates";
10
+ import { computeLineHash } from "../edit/line-hash";
11
+ import {
12
+ type ChunkReadTarget,
13
+ formatChunkedRead,
14
+ parseChunkReadPath,
15
+ parseChunkSelector,
16
+ resolveAnchorStyle,
17
+ } from "../edit/modes/chunk";
11
18
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
19
  import { parseInternalUrl } from "../internal-urls/parse";
13
20
  import type { InternalUrl } from "../internal-urls/types";
14
21
  import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
15
- import { computeLineHash } from "../patch/hashline";
16
22
  import readDescription from "../prompts/tools/read.md" with { type: "text" };
23
+ import readChunkDescription from "../prompts/tools/read-chunk.md" with { type: "text" };
17
24
  import type { ToolSession } from "../sdk";
18
25
  import {
19
26
  DEFAULT_MAX_BYTES,
@@ -25,20 +32,17 @@ import {
25
32
  } from "../session/streaming-output";
26
33
  import { renderCodeCell, renderStatusLine } from "../tui";
27
34
  import { CachedOutputBlock } from "../tui/output-block";
35
+ import { resolveEditMode } from "../utils/edit-mode";
28
36
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
29
- import {
30
- ImageInputTooLargeError,
31
- loadImageInput,
32
- MAX_IMAGE_INPUT_BYTES,
33
- readImageMetadata,
34
- } from "../utils/image-input";
37
+ import { ImageInputTooLargeError, loadImageInput, MAX_IMAGE_INPUT_BYTES } from "../utils/image-loading";
35
38
  import { convertFileWithMarkit } from "../utils/markit";
36
- import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
37
39
  import { type ArchiveReader, openArchive, parseArchivePathCandidates } from "./archive-reader";
40
+
38
41
  import {
39
42
  executeReadUrl,
40
43
  isReadableUrlPath,
41
44
  loadReadUrlCacheEntry,
45
+ parseReadUrlTarget,
42
46
  type ReadUrlToolDetails,
43
47
  renderReadUrlCall,
44
48
  renderReadUrlResult,
@@ -50,19 +54,14 @@ import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-util
50
54
  import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
51
55
  import { toolResult } from "./tool-result";
52
56
 
57
+ const PROSE_LANGUAGES = new Set(["markdown", "text", "log", "asciidoc", "restructuredtext"]);
58
+
59
+ function isProseLanguage(language: string | undefined): boolean {
60
+ return language !== undefined && PROSE_LANGUAGES.has(language);
61
+ }
62
+
53
63
  // Document types converted to markdown via markit.
54
- const CONVERTIBLE_EXTENSIONS = new Set([
55
- ".pdf",
56
- ".doc",
57
- ".docx",
58
- ".ppt",
59
- ".pptx",
60
- ".xls",
61
- ".xlsx",
62
- ".rtf",
63
- ".epub",
64
- ".ipynb",
65
- ]);
64
+ const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
66
65
 
67
66
  // Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs
68
67
  const REMOTE_MOUNT_PREFIX = getRemoteDir() + path.sep;
@@ -354,10 +353,8 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
354
353
 
355
354
  const readSchema = Type.Object({
356
355
  path: Type.String({ description: "Path or URL to read" }),
357
- offset: Type.Optional(Type.Number({ description: "Line number to start from (1-indexed)" })),
358
- limit: Type.Optional(Type.Number({ description: "Maximum number of lines" })),
356
+ sel: Type.Optional(Type.String({ description: "Selector: chunk path, L10-L50, or raw" })),
359
357
  timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
360
- raw: Type.Optional(Type.Boolean({ description: "If set, returns raw content without transformations" })),
361
358
  });
362
359
 
363
360
  export type ReadToolInput = Static<typeof readSchema>;
@@ -368,6 +365,7 @@ export interface ReadToolDetails {
368
365
  isDirectory?: boolean;
369
366
  resolvedPath?: string;
370
367
  suffixResolution?: { from: string; to: string };
368
+ chunk?: ChunkReadTarget;
371
369
  url?: string;
372
370
  finalUrl?: string;
373
371
  contentType?: string;
@@ -378,6 +376,43 @@ export interface ReadToolDetails {
378
376
 
379
377
  type ReadParams = ReadToolInput;
380
378
 
379
+ /** Parsed representation of the `sel` parameter. */
380
+ type ParsedSelector =
381
+ | { kind: "none" }
382
+ | { kind: "raw" }
383
+ | { kind: "lines"; startLine: number; endLine: number | undefined }
384
+ | { kind: "chunk"; selector: string };
385
+
386
+ const LINE_RANGE_RE = /^L(\d+)(?:-L?(\d+))?$/i;
387
+
388
+ function parseSel(sel: string | undefined): ParsedSelector {
389
+ if (!sel || sel.length === 0) return { kind: "none" };
390
+ const normalizedSelector = parseChunkSelector(sel).selector ?? sel;
391
+ if (normalizedSelector === "raw") return { kind: "raw" };
392
+ const lineMatch = LINE_RANGE_RE.exec(normalizedSelector);
393
+ if (lineMatch) {
394
+ const rawStart = Number.parseInt(lineMatch[1]!, 10);
395
+ if (rawStart < 1) {
396
+ throw new ToolError("L0 is invalid; lines are 1-indexed. Use sel=L1.");
397
+ }
398
+ const rawEnd = lineMatch[2] ? Number.parseInt(lineMatch[2], 10) : undefined;
399
+ if (rawEnd !== undefined && rawEnd < rawStart) {
400
+ throw new ToolError(`Invalid range L${rawStart}-L${rawEnd}: end must be >= start.`);
401
+ }
402
+ return { kind: "lines", startLine: rawStart, endLine: rawEnd };
403
+ }
404
+ return { kind: "chunk", selector: normalizedSelector };
405
+ }
406
+
407
+ /** Convert a line-range selector to the offset/limit pair used by internal pagination. */
408
+ function selToOffsetLimit(parsed: ParsedSelector): { offset?: number; limit?: number } {
409
+ if (parsed.kind === "lines") {
410
+ const limit = parsed.endLine !== undefined ? parsed.endLine - parsed.startLine + 1 : undefined;
411
+ return { offset: parsed.startLine, limit };
412
+ }
413
+ return {};
414
+ }
415
+
381
416
  interface ResolvedArchiveReadPath {
382
417
  absolutePath: string;
383
418
  archiveSubPath: string;
@@ -410,12 +445,17 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
410
445
  Math.min(session.settings.get("read.defaultLimit") ?? DEFAULT_MAX_LINES, DEFAULT_MAX_LINES),
411
446
  );
412
447
  this.#inspectImageEnabled = session.settings.get("inspect_image.enabled");
413
- this.description = renderPromptTemplate(readDescription, {
414
- DEFAULT_LIMIT: String(this.#defaultLimit),
415
- DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
416
- IS_HASHLINE_MODE: displayMode.hashLines,
417
- IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
418
- });
448
+ this.description =
449
+ resolveEditMode(session) === "chunk"
450
+ ? prompt.render(readChunkDescription, {
451
+ anchorStyle: resolveAnchorStyle(session.settings),
452
+ })
453
+ : prompt.render(readDescription, {
454
+ DEFAULT_LIMIT: String(this.#defaultLimit),
455
+ DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
456
+ IS_HASHLINE_MODE: displayMode.hashLines,
457
+ IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
458
+ });
419
459
  }
420
460
 
421
461
  async #resolveArchiveReadPath(readPath: string, signal?: AbortSignal): Promise<ResolvedArchiveReadPath | null> {
@@ -470,6 +510,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
470
510
  sourceUrl?: string;
471
511
  sourceInternal?: string;
472
512
  entityLabel: string;
513
+ ignoreResultLimits?: boolean;
473
514
  },
474
515
  ): AgentToolResult<ReadToolDetails> {
475
516
  const displayMode = resolveFileDisplayMode(this.session);
@@ -478,6 +519,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
478
519
  const totalLines = allLines.length;
479
520
  const startLine = offset ? Math.max(0, offset - 1) : 0;
480
521
  const startLineDisplay = startLine + 1;
522
+ const ignoreResultLimits = options.ignoreResultLimits ?? false;
481
523
 
482
524
  const resultBuilder = toolResult(details);
483
525
  if (options.sourcePath) {
@@ -494,18 +536,19 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
494
536
  const suggestion =
495
537
  allLines.length === 0
496
538
  ? `The ${options.entityLabel} is empty.`
497
- : `Use offset=1 to read from the start, or offset=${allLines.length} to read the last line.`;
539
+ : `Use sel=L1 to read from the start, or sel=L${allLines.length} to read the last line.`;
498
540
  return resultBuilder
499
541
  .text(
500
- `Offset ${offset} is beyond end of ${options.entityLabel} (${allLines.length} lines total). ${suggestion}`,
542
+ `Line ${startLineDisplay} is beyond end of ${options.entityLabel} (${allLines.length} lines total). ${suggestion}`,
501
543
  )
502
544
  .done();
503
545
  }
504
546
 
505
- const endLine = limit !== undefined ? Math.min(startLine + limit, allLines.length) : allLines.length;
547
+ const endLine =
548
+ limit !== undefined && !ignoreResultLimits ? Math.min(startLine + limit, allLines.length) : allLines.length;
506
549
  const selectedContent = allLines.slice(startLine, endLine).join("\n");
507
- const userLimitedLines = limit !== undefined ? endLine - startLine : undefined;
508
- const truncation = truncateHead(selectedContent);
550
+ const userLimitedLines = limit !== undefined && !ignoreResultLimits ? endLine - startLine : undefined;
551
+ const truncation = ignoreResultLimits ? noTruncResult(selectedContent) : truncateHead(selectedContent);
509
552
 
510
553
  const shouldAddHashLines = displayMode.hashLines;
511
554
  const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
@@ -553,7 +596,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
553
596
  const nextOffset = startLine + userLimitedLines + 1;
554
597
 
555
598
  outputText = formatText(selectedContent, startLineDisplay);
556
- outputText += `\n\n[${remaining} more lines in ${options.entityLabel}. Use offset=${nextOffset} to continue]`;
599
+ outputText += `\n\n[${remaining} more lines in ${options.entityLabel}. Use sel=L${nextOffset} to continue]`;
557
600
  } else {
558
601
  outputText = formatText(truncation.content, startLineDisplay);
559
602
  }
@@ -671,42 +714,60 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
671
714
  _onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
672
715
  _toolContext?: AgentToolContext,
673
716
  ): Promise<AgentToolResult<ReadToolDetails>> {
674
- let { path: readPath, offset, limit, timeout, raw } = params;
675
- const displayMode = resolveFileDisplayMode(this.session);
717
+ let { path: readPath, sel, timeout } = params;
676
718
  if (readPath.startsWith("file://")) {
677
719
  readPath = expandPath(readPath);
678
720
  }
721
+ const displayMode = resolveFileDisplayMode(this.session);
722
+ const chunkMode = resolveEditMode(this.session) === "chunk";
679
723
 
680
724
  // Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
681
725
  const internalRouter = this.session.internalRouter;
682
726
  if (internalRouter?.canHandle(readPath)) {
727
+ const parsed = parseSel(sel);
728
+ const { offset, limit } = selToOffsetLimit(parsed);
683
729
  return this.#handleInternalUrl(readPath, offset, limit);
684
730
  }
685
731
 
686
- if (isReadableUrlPath(readPath)) {
732
+ const parsedUrlTarget = parseReadUrlTarget(readPath, sel);
733
+ if (parsedUrlTarget) {
687
734
  if (!this.session.settings.get("fetch.enabled")) {
688
735
  throw new ToolError("URL reads are disabled by settings.");
689
736
  }
690
- if (offset !== undefined || limit !== undefined) {
691
- const cached = await loadReadUrlCacheEntry(this.session, { path: readPath, timeout, raw }, signal, {
692
- ensureArtifact: true,
693
- preferCached: true,
694
- });
695
- return this.#buildInMemoryTextResult(cached.output, offset, limit, {
737
+ if (parsedUrlTarget.offset !== undefined || parsedUrlTarget.limit !== undefined) {
738
+ const cached = await loadReadUrlCacheEntry(
739
+ this.session,
740
+ { path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw },
741
+ signal,
742
+ {
743
+ ensureArtifact: true,
744
+ preferCached: true,
745
+ },
746
+ );
747
+ return this.#buildInMemoryTextResult(cached.output, parsedUrlTarget.offset, parsedUrlTarget.limit, {
696
748
  details: { ...cached.details },
697
749
  sourceUrl: cached.details.finalUrl,
698
750
  entityLabel: "URL output",
699
751
  });
700
752
  }
701
- return executeReadUrl(this.session, { path: readPath, timeout, raw }, signal);
753
+ return executeReadUrl(this.session, { path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw }, signal);
702
754
  }
703
755
 
704
- const archivePath = await this.#resolveArchiveReadPath(readPath, signal);
756
+ const parsedReadPath = chunkMode ? parseChunkReadPath(readPath) : { filePath: readPath };
757
+ const localReadPath = parsedReadPath.filePath;
758
+ const pathSelectorParsed = chunkMode ? parseSel(parsedReadPath.selector) : { kind: "none" as const };
759
+ const pathChunkSelector = pathSelectorParsed.kind === "chunk" ? pathSelectorParsed.selector : undefined;
760
+ const selectorInput = sel ?? parsedReadPath.selector;
761
+ const rawSelectorInput = sel ?? parsedReadPath.selector;
762
+ const parsed = parseSel(selectorInput);
763
+
764
+ const archivePath = await this.#resolveArchiveReadPath(localReadPath, signal);
705
765
  if (archivePath) {
766
+ const { offset, limit } = selToOffsetLimit(parsed);
706
767
  return this.#readArchive(readPath, offset, limit, archivePath, signal);
707
768
  }
708
769
 
709
- let absolutePath = resolveReadPath(readPath, this.session.cwd);
770
+ let absolutePath = resolveReadPath(localReadPath, this.session.cwd);
710
771
  let suffixResolution: { from: string; to: string } | undefined;
711
772
 
712
773
  let isDirectory = false;
@@ -719,14 +780,14 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
719
780
  if (isNotFoundError(error)) {
720
781
  // Attempt unique suffix resolution before falling back to fuzzy suggestions
721
782
  if (!isRemoteMountPath(absolutePath)) {
722
- const suffixMatch = await findUniqueSuffixMatch(readPath, this.session.cwd, signal);
783
+ const suffixMatch = await findUniqueSuffixMatch(localReadPath, this.session.cwd, signal);
723
784
  if (suffixMatch) {
724
785
  try {
725
786
  const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
726
787
  absolutePath = suffixMatch.absolutePath;
727
788
  fileSize = retryStat.size;
728
789
  isDirectory = retryStat.isDirectory();
729
- suffixResolution = { from: readPath, to: suffixMatch.displayPath };
790
+ suffixResolution = { from: localReadPath, to: suffixMatch.displayPath };
730
791
  } catch {
731
792
  // Suffix match candidate no longer stats — fall through to error path
732
793
  }
@@ -734,7 +795,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
734
795
  }
735
796
 
736
797
  if (!suffixResolution) {
737
- throw new ToolError(`Path '${readPath}' not found`);
798
+ throw new ToolError(`Path '${localReadPath}' not found`);
738
799
  }
739
800
  } else {
740
801
  throw error;
@@ -742,7 +803,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
742
803
  }
743
804
 
744
805
  if (isDirectory) {
745
- const dirResult = await this.#readDirectory(absolutePath, limit, signal);
806
+ const dirResult = await this.#readDirectory(absolutePath, selToOffsetLimit(parsed).limit, signal);
746
807
  if (suffixResolution) {
747
808
  dirResult.details ??= {};
748
809
  dirResult.details.suffixResolution = suffixResolution;
@@ -750,11 +811,59 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
750
811
  return dirResult;
751
812
  }
752
813
 
753
- const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
814
+ const imageMetadata = await readImageMetadata(absolutePath);
815
+ const mimeType = imageMetadata?.mimeType;
754
816
  const ext = path.extname(absolutePath).toLowerCase();
817
+ const hasEditTool = this.session.hasEditTool ?? true;
818
+ const language = getLanguageFromPath(absolutePath);
819
+ const skipChunksForExplore = !hasEditTool && !this.session.settings.get("read.explorechunks");
820
+ const skipChunksForProse = isProseLanguage(language) && !this.session.settings.get("read.prosechunks");
821
+ const shouldConvertWithMarkit =
822
+ CONVERTIBLE_EXTENSIONS.has(ext) || (ext === ".ipynb" && (parsed.kind === "raw" || !chunkMode));
823
+
824
+ if (chunkMode && parsed.kind !== "raw" && !skipChunksForExplore && !skipChunksForProse) {
825
+ const absoluteLineRange =
826
+ pathChunkSelector && parsed.kind === "lines"
827
+ ? { startLine: parsed.startLine, endLine: parsed.endLine }
828
+ : undefined;
829
+ // sel= wins over path:chunk when both are provided (explicit param > embedded path).
830
+ const effectiveSelector = sel ? selectorInput : (pathChunkSelector ?? selectorInput);
831
+ const rawEffectiveSelector = sel ? selectorInput : (rawSelectorInput ?? effectiveSelector);
832
+ const chunkReadPath =
833
+ parsed.kind === "chunk" || (pathChunkSelector && !sel)
834
+ ? rawEffectiveSelector
835
+ ? `${localReadPath}:${rawEffectiveSelector}`
836
+ : localReadPath
837
+ : parsed.kind === "lines"
838
+ ? parsed.endLine !== undefined
839
+ ? `${localReadPath}:L${parsed.startLine}-L${parsed.endLine}`
840
+ : `${localReadPath}:L${parsed.startLine}`
841
+ : localReadPath;
842
+ const chunkResult = await formatChunkedRead({
843
+ filePath: absolutePath,
844
+ readPath: chunkReadPath,
845
+ cwd: this.session.cwd,
846
+ language,
847
+ omitChecksum: !hasEditTool,
848
+ anchorStyle: resolveAnchorStyle(this.session.settings),
849
+ absoluteLineRange,
850
+ });
851
+ let text = chunkResult.text;
852
+ if (suffixResolution) {
853
+ text = prependSuffixResolutionNotice(text, suffixResolution);
854
+ }
855
+ return toolResult<ReadToolDetails>({
856
+ resolvedPath: absolutePath,
857
+ suffixResolution,
858
+ chunk: chunkResult.chunk,
859
+ })
860
+ .text(text)
861
+ .sourcePath(absolutePath)
862
+ .done();
863
+ }
755
864
 
756
865
  // Read the file based on type
757
- let content: (TextContent | ImageContent)[];
866
+ let content: Array<TextContent | ImageContent>;
758
867
  let details: ReadToolDetails = {};
759
868
  let sourcePath: string | undefined;
760
869
  let truncationInfo:
@@ -763,14 +872,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
763
872
 
764
873
  if (mimeType) {
765
874
  if (this.#inspectImageEnabled) {
766
- const metadata = await readImageMetadata({
767
- path: readPath,
768
- cwd: this.session.cwd,
769
- resolvedPath: absolutePath,
770
- detectedMimeType: mimeType,
771
- });
875
+ const metadata = imageMetadata;
772
876
  const outputMime = metadata?.mimeType ?? mimeType;
773
- const outputBytes = metadata?.bytes ?? fileSize;
877
+ const outputBytes = fileSize;
774
878
  const metadataLines = [
775
879
  "Image metadata:",
776
880
  `- MIME: ${outputMime}`,
@@ -821,7 +925,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
821
925
  throw error;
822
926
  }
823
927
  }
824
- } else if (CONVERTIBLE_EXTENSIONS.has(ext)) {
928
+ } else if (shouldConvertWithMarkit) {
825
929
  // Convert document or notebook via markit.
826
930
  const result = await convertFileWithMarkit(absolutePath, signal);
827
931
  if (result.ok) {
@@ -840,13 +944,41 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
840
944
  content = [{ type: "text", text: `[Cannot read ${ext} file: conversion failed]` }];
841
945
  }
842
946
  } else {
843
- // Read as text using streaming to avoid loading huge files into memory
947
+ // Chunk mode: dispatch to chunk tree unless raw or line range requested
948
+ if (chunkMode && parsed.kind !== "raw" && parsed.kind !== "lines") {
949
+ const chunkSel = parsed.kind === "chunk" ? parsed.selector : undefined;
950
+ const chunkResult = await formatChunkedRead({
951
+ filePath: absolutePath,
952
+ readPath: chunkSel ? `${localReadPath}:${chunkSel}` : localReadPath,
953
+ cwd: this.session.cwd,
954
+ language: getLanguageFromPath(absolutePath),
955
+ omitChecksum: !(this.session.hasEditTool ?? true),
956
+ anchorStyle: resolveAnchorStyle(this.session.settings),
957
+ });
958
+ let text = chunkResult.text;
959
+ if (suffixResolution) {
960
+ text = prependSuffixResolutionNotice(text, suffixResolution);
961
+ }
962
+ return toolResult<ReadToolDetails>({
963
+ resolvedPath: absolutePath,
964
+ suffixResolution,
965
+ chunk: chunkResult.chunk,
966
+ })
967
+ .text(text)
968
+ .sourcePath(absolutePath)
969
+ .done();
970
+ }
971
+
972
+ // Raw text or line-range mode
973
+ const { offset, limit } = selToOffsetLimit(parsed);
844
974
  const startLine = offset ? Math.max(0, offset - 1) : 0;
845
- const startLineDisplay = startLine + 1; // For display (1-indexed)
975
+ const startLineDisplay = startLine + 1;
846
976
 
847
- const effectiveLimit = limit ?? this.#defaultLimit;
977
+ const DEFAULT_LIMIT = this.#defaultLimit;
978
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
848
979
  const maxLinesToCollect = Math.min(effectiveLimit, DEFAULT_MAX_LINES);
849
980
  const selectedLineLimit = effectiveLimit;
981
+
850
982
  const streamResult = await streamLinesFromFile(
851
983
  absolutePath,
852
984
  startLine,
@@ -870,9 +1002,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
870
1002
  const suggestion =
871
1003
  totalFileLines === 0
872
1004
  ? "The file is empty."
873
- : `Use offset=1 to read from the start, or offset=${totalFileLines} to read the last line.`;
1005
+ : `Use sel=L1 to read from the start, or sel=L${totalFileLines} to read the last line.`;
874
1006
  return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
875
- .text(`Offset ${offset} is beyond end of file (${totalFileLines} lines total). ${suggestion}`)
1007
+ .text(`Line ${startLineDisplay} is beyond end of file (${totalFileLines} lines total). ${suggestion}`)
876
1008
  .done();
877
1009
  }
878
1010
 
@@ -939,7 +1071,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
939
1071
  const nextOffset = startLine + userLimitedLines + 1;
940
1072
 
941
1073
  outputText = formatText(truncation.content, startLineDisplay);
942
- outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;
1074
+ outputText += `\n\n[${remaining} more lines in file. Use sel=L${nextOffset} to continue]`;
943
1075
  details = {};
944
1076
  sourcePath = absolutePath;
945
1077
  } else {
@@ -1011,82 +1143,13 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1011
1143
  return toolResult(details).text(resource.content).sourceInternal(url).done();
1012
1144
  }
1013
1145
 
1014
- // Apply pagination similar to file reading.
1015
- const allLines = resource.content.split("\n");
1016
- const totalLines = allLines.length;
1017
-
1018
- const startLine = offset ? Math.max(0, offset - 1) : 0;
1019
- const startLineDisplay = startLine + 1;
1020
-
1021
- if (startLine >= allLines.length) {
1022
- const suggestion =
1023
- allLines.length === 0
1024
- ? "The resource is empty."
1025
- : `Use offset=1 to read from the start, or offset=${allLines.length} to read the last line.`;
1026
- return toolResult<ReadToolDetails>(details)
1027
- .text(`Offset ${offset} is beyond end of resource (${allLines.length} lines total). ${suggestion}`)
1028
- .done();
1029
- }
1030
-
1031
- const ignoreLimits = scheme === "skill";
1032
- let selectedContent: string;
1033
- let userLimitedLines: number | undefined;
1034
- if (limit !== undefined && !ignoreLimits) {
1035
- const endLine = Math.min(startLine + limit, allLines.length);
1036
- selectedContent = allLines.slice(startLine, endLine).join("\n");
1037
- userLimitedLines = endLine - startLine;
1038
- } else {
1039
- selectedContent = allLines.slice(startLine).join("\n");
1040
- }
1041
-
1042
- const truncation: TruncationResult = ignoreLimits
1043
- ? noTruncResult(selectedContent)
1044
- : truncateHead(selectedContent);
1045
-
1046
- let outputText: string;
1047
- let truncationInfo:
1048
- | { result: TruncationResult; options: { direction: "head"; startLine?: number; totalFileLines?: number } }
1049
- | undefined;
1050
-
1051
- if (truncation.firstLineExceedsLimit) {
1052
- const firstLine = allLines[startLine] ?? "";
1053
- const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
1054
- const snippet = truncateHeadBytes(firstLine, DEFAULT_MAX_BYTES);
1055
-
1056
- outputText = snippet.text;
1057
- if (snippet.text.length === 0) {
1058
- outputText = `[Line ${startLineDisplay} is ${formatBytes(
1059
- firstLineBytes,
1060
- )}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
1061
- }
1062
- details.truncation = truncation;
1063
- truncationInfo = {
1064
- result: truncation,
1065
- options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
1066
- };
1067
- } else if (truncation.truncated) {
1068
- outputText = truncation.content;
1069
- details.truncation = truncation;
1070
- truncationInfo = {
1071
- result: truncation,
1072
- options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
1073
- };
1074
- } else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
1075
- const remaining = allLines.length - (startLine + userLimitedLines);
1076
- const nextOffset = startLine + userLimitedLines + 1;
1077
-
1078
- outputText = truncation.content;
1079
- outputText += `\n\n[${remaining} more lines in resource. Use offset=${nextOffset} to continue]`;
1080
- details.truncation = truncation;
1081
- } else {
1082
- outputText = truncation.content;
1083
- }
1084
-
1085
- const resultBuilder = toolResult(details).text(outputText).sourceInternal(url);
1086
- if (truncationInfo) {
1087
- resultBuilder.truncation(truncationInfo.result, truncationInfo.options);
1088
- }
1089
- return resultBuilder.done();
1146
+ return this.#buildInMemoryTextResult(resource.content, offset, limit, {
1147
+ details,
1148
+ sourcePath: resource.sourcePath,
1149
+ sourceInternal: url,
1150
+ entityLabel: "resource",
1151
+ ignoreResultLimits: scheme === "skill",
1152
+ });
1090
1153
  }
1091
1154
 
1092
1155
  /** Read directory contents as a formatted listing */
@@ -1166,9 +1229,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1166
1229
  interface ReadRenderArgs {
1167
1230
  path?: string;
1168
1231
  file_path?: string;
1232
+ sel?: string;
1233
+ timeout?: number;
1234
+ // Legacy fields from old schema — tolerated for in-flight tool calls during transition
1169
1235
  offset?: number;
1170
1236
  limit?: number;
1171
- timeout?: number;
1172
1237
  raw?: boolean;
1173
1238
  }
1174
1239
 
@@ -1,7 +1,6 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import { type MermaidAsciiRenderOptions, renderMermaidAscii } from "@oh-my-pi/pi-utils";
2
+ import { type MermaidAsciiRenderOptions, prompt, renderMermaidAscii } from "@oh-my-pi/pi-utils";
3
3
  import { type Static, Type } from "@sinclair/typebox";
4
- import { renderPromptTemplate } from "../config/prompt-templates";
5
4
  import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with { type: "text" };
6
5
  import type { ToolSession } from "./index";
7
6
 
@@ -41,7 +40,7 @@ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema,
41
40
  readonly strict = true;
42
41
 
43
42
  constructor(private readonly session: ToolSession) {
44
- this.description = renderPromptTemplate(renderMermaidDescription);
43
+ this.description = prompt.render(renderMermaidDescription);
45
44
  }
46
45
 
47
46
  async execute(
@@ -5,17 +5,17 @@
5
5
  * tool renderers to ensure a unified TUI experience.
6
6
  */
7
7
  import * as os from "node:os";
8
- import { type Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
9
- import { getIndentation, pluralize } from "@oh-my-pi/pi-utils";
8
+ import * as path from "node:path";
9
+ import type { Ellipsis } from "@oh-my-pi/pi-natives";
10
+ import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
11
+ import { pluralize } from "@oh-my-pi/pi-utils";
10
12
  import { settings } from "../config/settings";
11
13
  import type { Theme } from "../modes/theme/theme";
12
14
  import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
13
15
 
14
- export { Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
16
+ export { Ellipsis } from "@oh-my-pi/pi-natives";
17
+ export { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
15
18
 
16
- export function replaceTabs(text: string, file?: string): string {
17
- return text.replaceAll("\t", getIndentation(file));
18
- }
19
19
  // =============================================================================
20
20
  // Standardized Display Constants
21
21
  // =============================================================================
@@ -548,6 +548,20 @@ export function shortenPath(filePath: string, homeDir?: string): string {
548
548
  return filePath;
549
549
  }
550
550
 
551
+ export function formatToolWorkingDirectory(workdir: string | undefined, projectDir: string): string | undefined {
552
+ if (!workdir) return undefined;
553
+ const resolvedProjectDir = path.resolve(projectDir);
554
+ const resolvedWorkdir = path.resolve(projectDir, workdir);
555
+ if (resolvedWorkdir === resolvedProjectDir) {
556
+ return undefined;
557
+ }
558
+ const relativePath = path.relative(resolvedProjectDir, resolvedWorkdir);
559
+ const isWithinProject =
560
+ relativePath.length > 0 && !relativePath.startsWith("..") && !relativePath.startsWith(`..${path.sep}`);
561
+ const displayWorkdir = isWithinProject ? relativePath : shortenPath(resolvedWorkdir);
562
+ return replaceTabs(displayWorkdir);
563
+ }
564
+
551
565
  export function formatScreenshot(opts: {
552
566
  saveFullRes: boolean;
553
567
  savedMimeType: string;
@@ -4,10 +4,10 @@
4
4
  * These provide rich visualization for tool calls and results in the TUI.
5
5
  */
6
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
+ import { editToolRenderer } from "../edit/renderer";
7
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
8
9
  import { lspToolRenderer } from "../lsp/render";
9
10
  import type { Theme } from "../modes/theme/theme";
10
- import { editToolRenderer } from "../patch";
11
11
  import { taskToolRenderer } from "../task/render";
12
12
  import { webSearchToolRenderer } from "../web/search/render";
13
13
  import { askToolRenderer } from "./ask";
@@ -15,6 +15,7 @@ import { astEditToolRenderer } from "./ast-edit";
15
15
  import { astGrepToolRenderer } from "./ast-grep";
16
16
  import { bashToolRenderer } from "./bash";
17
17
  import { calculatorToolRenderer } from "./calculator";
18
+ import { debugToolRenderer } from "./debug";
18
19
  import { findToolRenderer } from "./find";
19
20
  import { ghRunWatchToolRenderer } from "./gh-renderer";
20
21
  import { grepToolRenderer } from "./grep";
@@ -46,6 +47,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
46
47
  ast_grep: astGrepToolRenderer as ToolRenderer,
47
48
  ast_edit: astEditToolRenderer as ToolRenderer,
48
49
  bash: bashToolRenderer as ToolRenderer,
50
+ debug: debugToolRenderer as ToolRenderer,
49
51
  python: pythonToolRenderer as ToolRenderer,
50
52
  calc: calculatorToolRenderer as ToolRenderer,
51
53
  edit: editToolRenderer as ToolRenderer,