@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
@@ -8,6 +8,10 @@ import type {
8
8
  import type { AgentSessionEvent } from "../../session/agent-session";
9
9
  import type { TodoStatus } from "../../tools/todo-write";
10
10
 
11
+ interface AcpEventMapperOptions {
12
+ getMessageId?: (message: unknown) => string | undefined;
13
+ }
14
+
11
15
  interface ContentArrayContainer {
12
16
  content?: unknown;
13
17
  }
@@ -118,10 +122,11 @@ export function mapToolKind(toolName: string): ToolKind {
118
122
  export function mapAgentSessionEventToAcpSessionUpdates(
119
123
  event: AgentSessionEvent,
120
124
  sessionId: string,
125
+ options: AcpEventMapperOptions = {},
121
126
  ): SessionNotification[] {
122
127
  switch (event.type) {
123
128
  case "message_update":
124
- return mapAssistantMessageUpdate(event, sessionId);
129
+ return mapAssistantMessageUpdate(event, sessionId, options);
125
130
  case "tool_execution_start": {
126
131
  const update: SessionUpdate = {
127
132
  sessionUpdate: "tool_call",
@@ -181,6 +186,7 @@ export function mapAgentSessionEventToAcpSessionUpdates(
181
186
  function mapAssistantMessageUpdate(
182
187
  event: Extract<AgentSessionEvent, { type: "message_update" }>,
183
188
  sessionId: string,
189
+ options: AcpEventMapperOptions,
184
190
  ): SessionNotification[] {
185
191
  if (!isAssistantMessage(event.message)) {
186
192
  return [];
@@ -208,10 +214,12 @@ function mapAssistantMessageUpdate(
208
214
  return [];
209
215
  }
210
216
 
217
+ const messageId = options.getMessageId?.(event.message);
211
218
  return [
212
219
  toSessionNotification(sessionId, {
213
220
  sessionUpdate,
214
221
  content: { type: "text", text },
222
+ messageId,
215
223
  }),
216
224
  ];
217
225
  }
@@ -3,11 +3,13 @@ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
3
3
  import type { AgentSession } from "../../session/agent-session";
4
4
  import { AcpAgent } from "./acp-agent";
5
5
 
6
- export async function runAcpMode(session: AgentSession): Promise<never> {
6
+ export type AcpSessionFactory = (cwd: string) => Promise<AgentSession>;
7
+
8
+ export async function runAcpMode(session: AgentSession, createSession: AcpSessionFactory): Promise<never> {
7
9
  const input = stream.Writable.toWeb(process.stdout);
8
10
  const output = stream.Readable.toWeb(process.stdin);
9
11
  const transport = ndJsonStream(input, output);
10
- const connection = new AgentSideConnection(conn => new AcpAgent(conn, session), transport);
12
+ const connection = new AgentSideConnection(conn => new AcpAgent(conn, session, createSession), transport);
11
13
  await connection.closed;
12
14
  process.exit(0);
13
15
  }
@@ -31,7 +31,7 @@ import {
31
31
  visibleWidth,
32
32
  wrapTextWithAnsi,
33
33
  } from "@oh-my-pi/pi-tui";
34
- import { isEnoent } from "@oh-my-pi/pi-utils";
34
+ import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
35
35
  import { YAML } from "bun";
36
36
  import { getConfigDirs } from "../../config";
37
37
  import type { ModelRegistry } from "../../config/model-registry";
@@ -41,7 +41,6 @@ import {
41
41
  resolveConfiguredModelPatterns,
42
42
  resolveModelOverride,
43
43
  } from "../../config/model-resolver";
44
- import { renderPromptTemplate } from "../../config/prompt-templates";
45
44
  import { Settings } from "../../config/settings";
46
45
  import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-architect.md" with { type: "text" };
47
46
  import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
@@ -627,8 +626,8 @@ export class AgentDashboard extends Container {
627
626
  throw new Error("No available model to generate agent specification.");
628
627
  }
629
628
 
630
- const systemPrompt = renderPromptTemplate(agentCreationArchitectPrompt, { TASK_TOOL_NAME: "task" });
631
- const userPrompt = renderPromptTemplate(agentCreationUserPrompt, { request: description });
629
+ const systemPrompt = prompt.render(agentCreationArchitectPrompt, { TASK_TOOL_NAME: "task" });
630
+ const userPrompt = prompt.render(agentCreationUserPrompt, { request: description });
632
631
 
633
632
  const { session } = await createAgentSession({
634
633
  cwd: this.cwd,
@@ -1,4 +1,4 @@
1
- import { getIndentation } from "@oh-my-pi/pi-utils";
1
+ import { getIndentation } from "@oh-my-pi/pi-natives";
2
2
  import * as Diff from "diff";
3
3
  import { theme } from "../../modes/theme/theme";
4
4
  import { replaceTabs } from "../../tools/render-utils";
@@ -15,11 +15,10 @@ const DIM_OFF = "\x1b[22m";
15
15
  */
16
16
  function visualizeIndent(text: string, filePath?: string): string {
17
17
  const match = text.match(/^([ \t]+)/);
18
- if (!match) return replaceTabs(text, filePath);
18
+ if (!match) return replaceTabs(text);
19
19
  const indent = match[1];
20
20
  const rest = text.slice(indent.length);
21
- const indentation = getIndentation(filePath);
22
- const tabWidth = indentation.length;
21
+ const tabWidth = getIndentation(filePath);
23
22
  const leftPadding = Math.floor(tabWidth / 2);
24
23
  const rightPadding = Math.max(0, tabWidth - leftPadding - 1);
25
24
  const tabMarker = `${DIM}${" ".repeat(leftPadding)}→${" ".repeat(rightPadding)}${DIM_OFF}`;
@@ -31,7 +30,7 @@ function visualizeIndent(text: string, filePath?: string): string {
31
30
  visible += `${DIM}·${DIM_OFF}`;
32
31
  }
33
32
  }
34
- return `${visible}${replaceTabs(rest, filePath)}`;
33
+ return `${visible}${replaceTabs(rest)}`;
35
34
  }
36
35
 
37
36
  /**
@@ -154,8 +153,8 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
154
153
  const added = addedLines[0];
155
154
 
156
155
  const { removedLine, addedLine } = renderIntraLineDiff(
157
- replaceTabs(removed.content, options.filePath),
158
- replaceTabs(added.content, options.filePath),
156
+ replaceTabs(removed.content),
157
+ replaceTabs(added.content),
159
158
  );
160
159
 
161
160
  result.push(
@@ -5,7 +5,8 @@ import { formatNumber, getProjectDir } from "@oh-my-pi/pi-utils";
5
5
  import { theme } from "../../modes/theme/theme";
6
6
  import type { AgentSession } from "../../session/agent-session";
7
7
  import { shortenPath } from "../../tools/render-utils";
8
- import { findGitHeadPathAsync, sanitizeStatusText } from "../shared";
8
+ import * as git from "../../utils/git";
9
+ import { sanitizeStatusText } from "../shared";
9
10
  import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
10
11
 
11
12
  /**
@@ -55,13 +56,13 @@ export class FooterComponent implements Component {
55
56
  this.#gitWatcher = null;
56
57
  }
57
58
 
58
- findGitHeadPathAsync().then(result => {
59
- if (!result) {
59
+ git.head.resolve(getProjectDir()).then(head => {
60
+ if (!head) {
60
61
  return;
61
62
  }
62
63
 
63
64
  try {
64
- this.#gitWatcher = fs.watch(result.path, () => {
65
+ this.#gitWatcher = fs.watch(head.headPath, () => {
65
66
  this.#cachedBranch = undefined; // Invalidate cache
66
67
  if (this.#onBranchChange) {
67
68
  this.#onBranchChange();
@@ -93,35 +94,14 @@ export class FooterComponent implements Component {
93
94
  * Returns null if not in a git repo, branch name otherwise.
94
95
  */
95
96
  #getCurrentBranch(): string | null {
96
- // Return cached value if available
97
97
  if (this.#cachedBranch !== undefined) {
98
98
  return this.#cachedBranch;
99
99
  }
100
100
 
101
- // Note: fire-and-forget async call - will return undefined on first call
102
- // This is acceptable since it's a cached value that will update on next render
103
- findGitHeadPathAsync().then(result => {
104
- if (!result) {
105
- this.#cachedBranch = null;
106
- if (this.#onBranchChange) {
107
- this.#onBranchChange();
108
- }
109
- return;
110
- }
111
- const content = result.content.trim();
112
-
113
- if (content.startsWith("ref: refs/heads/")) {
114
- this.#cachedBranch = content.slice(16);
115
- } else {
116
- this.#cachedBranch = "detached";
117
- }
118
- if (this.#onBranchChange) {
119
- this.#onBranchChange();
120
- }
121
- });
122
-
123
- // Return undefined while loading (will show on next render once loaded)
124
- return null;
101
+ const headState = git.head.resolveSync(getProjectDir());
102
+ this.#cachedBranch =
103
+ headState === null ? null : headState.kind === "ref" ? (headState.branchName ?? headState.ref) : "detached";
104
+ return this.#cachedBranch;
125
105
  }
126
106
 
127
107
  render(width: number): string[] {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
10
10
  import { getEditorTheme, theme } from "../../modes/theme/theme";
11
- import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
11
+ import { matchesAppExternalEditor, matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
12
12
  import { getEditorCommand, openInEditor } from "../../utils/external-editor";
13
13
  import { DynamicBorder } from "./dynamic-border";
14
14
 
@@ -87,7 +87,7 @@ export class HookEditorComponent extends Container {
87
87
  }
88
88
 
89
89
  // Ctrl+G for external editor
90
- if (matchesKey(keyData, "ctrl+g")) {
90
+ if (matchesAppExternalEditor(keyData)) {
91
91
  void this.#openExternalEditor();
92
92
  return;
93
93
  }
@@ -123,7 +123,7 @@ export class HookEditorComponent extends Container {
123
123
  }
124
124
 
125
125
  // Ctrl+G for external editor
126
- if (matchesKey(keyData, "ctrl+g")) {
126
+ if (matchesAppExternalEditor(keyData)) {
127
127
  void this.#openExternalEditor();
128
128
  return;
129
129
  }
@@ -16,7 +16,7 @@ import {
16
16
  visibleWidth,
17
17
  } from "@oh-my-pi/pi-tui";
18
18
  import { getMarkdownTheme, theme } from "../../modes/theme/theme";
19
- import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
19
+ import { matchesAppExternalEditor, matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
20
20
  import { CountdownTimer } from "./countdown-timer";
21
21
  import { DynamicBorder } from "./dynamic-border";
22
22
 
@@ -29,6 +29,7 @@ export interface HookSelectorOptions {
29
29
  maxVisible?: number;
30
30
  onLeft?: () => void;
31
31
  onRight?: () => void;
32
+ onExternalEditor?: () => void;
32
33
  helpText?: string;
33
34
  }
34
35
 
@@ -67,6 +68,7 @@ export class HookSelectorComponent extends Container {
67
68
  #countdown: CountdownTimer | undefined;
68
69
  #onLeftCallback: (() => void) | undefined;
69
70
  #onRightCallback: (() => void) | undefined;
71
+ #onExternalEditorCallback: (() => void) | undefined;
70
72
  constructor(
71
73
  title: string,
72
74
  options: string[],
@@ -84,6 +86,7 @@ export class HookSelectorComponent extends Container {
84
86
  this.#baseTitle = title;
85
87
  this.#onLeftCallback = opts?.onLeft;
86
88
  this.#onRightCallback = opts?.onRight;
89
+ this.#onExternalEditorCallback = opts?.onExternalEditor;
87
90
 
88
91
  this.addChild(new DynamicBorder());
89
92
  this.addChild(new Spacer(1));
@@ -174,6 +177,8 @@ export class HookSelectorComponent extends Container {
174
177
  this.#onLeftCallback?.();
175
178
  } else if (matchesKey(keyData, "right")) {
176
179
  this.#onRightCallback?.();
180
+ } else if (this.#onExternalEditorCallback && matchesAppExternalEditor(keyData)) {
181
+ this.#onExternalEditorCallback();
177
182
  } else if (matchesSelectCancel(keyData)) {
178
183
  this.#onCancelCallback();
179
184
  }
@@ -7,8 +7,7 @@ import type { ToolExecutionHandle } from "./tool-execution";
7
7
  type ReadRenderArgs = {
8
8
  path?: string;
9
9
  file_path?: string;
10
- offset?: number;
11
- limit?: number;
10
+ sel?: string;
12
11
  };
13
12
 
14
13
  type ReadToolSuffixResolution = {
@@ -33,8 +32,7 @@ function getSuffixResolution(details: ReadToolResultDetails | undefined): ReadTo
33
32
  type ReadEntry = {
34
33
  toolCallId: string;
35
34
  path: string;
36
- offset?: number;
37
- limit?: number;
35
+ sel?: string;
38
36
  status: "pending" | "success" | "warning" | "error";
39
37
  correctedFrom?: string;
40
38
  };
@@ -56,13 +54,11 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
56
54
  const entry: ReadEntry = this.#entries.get(toolCallId) ?? {
57
55
  toolCallId,
58
56
  path: rawPath,
59
- offset: args.offset,
60
- limit: args.limit,
57
+ sel: args.sel,
61
58
  status: "pending",
62
59
  };
63
60
  entry.path = rawPath;
64
- entry.offset = args.offset;
65
- entry.limit = args.limit;
61
+ entry.sel = args.sel;
66
62
  this.#entries.set(toolCallId, entry);
67
63
  this.#updateDisplay();
68
64
  }
@@ -132,10 +128,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
132
128
  #formatPath(entry: ReadEntry): string {
133
129
  const filePath = shortenPath(entry.path);
134
130
  let pathDisplay = filePath ? theme.fg("accent", filePath) : theme.fg("toolOutput", "…");
135
- if (entry.offset !== undefined || entry.limit !== undefined) {
136
- const startLine = entry.offset ?? 1;
137
- const endLine = entry.limit !== undefined ? startLine + entry.limit - 1 : "";
138
- pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
131
+ if (entry.sel) {
132
+ pathDisplay += theme.fg("warning", `:${entry.sel}`);
139
133
  }
140
134
  if (entry.correctedFrom) {
141
135
  pathDisplay += theme.fg("dim", ` (corrected from ${shortenPath(entry.correctedFrom)})`);