@oh-my-pi/pi-coding-agent 14.5.2 → 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 (69) hide show
  1. package/CHANGELOG.md +70 -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 +104 -6
  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 +1094 -642
  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 +13 -43
  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/controllers/event-controller.ts +12 -0
  33. package/src/modes/utils/ui-helpers.ts +31 -7
  34. package/src/prompts/agents/explore.md +1 -1
  35. package/src/prompts/agents/librarian.md +2 -2
  36. package/src/prompts/agents/plan.md +2 -2
  37. package/src/prompts/agents/reviewer.md +1 -1
  38. package/src/prompts/agents/task.md +2 -2
  39. package/src/prompts/system/plan-mode-active.md +1 -1
  40. package/src/prompts/system/system-prompt.md +34 -31
  41. package/src/prompts/tools/apply-patch.md +0 -2
  42. package/src/prompts/tools/atom.md +88 -97
  43. package/src/prompts/tools/bash.md +7 -4
  44. package/src/prompts/tools/checkpoint.md +1 -1
  45. package/src/prompts/tools/find.md +6 -1
  46. package/src/prompts/tools/hashline.md +10 -11
  47. package/src/prompts/tools/patch.md +13 -13
  48. package/src/prompts/tools/read.md +5 -5
  49. package/src/prompts/tools/replace.md +3 -3
  50. package/src/prompts/tools/{grep.md → search.md} +4 -4
  51. package/src/sdk.ts +19 -9
  52. package/src/session/agent-session.ts +69 -1
  53. package/src/system-prompt.ts +15 -5
  54. package/src/task/executor.ts +5 -0
  55. package/src/task/index.ts +10 -1
  56. package/src/tools/ast-edit.ts +27 -50
  57. package/src/tools/ast-grep.ts +22 -48
  58. package/src/tools/bash.ts +1 -1
  59. package/src/tools/file-recorder.ts +6 -6
  60. package/src/tools/find.ts +11 -13
  61. package/src/tools/grouped-file-output.ts +96 -0
  62. package/src/tools/index.ts +7 -7
  63. package/src/tools/path-utils.ts +31 -4
  64. package/src/tools/read.ts +12 -6
  65. package/src/tools/renderers.ts +2 -2
  66. package/src/tools/{grep.ts → search.ts} +43 -86
  67. package/src/tools/todo-write.ts +0 -1
  68. package/src/tools/write.ts +8 -4
  69. package/src/web/search/index.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,76 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.5.5] - 2026-04-29
6
+ ### Breaking Changes
7
+
8
+ - Rejected atom diffs with unrecognized operations (including lone '-' lines) by throwing parse errors instead of treating them as inserts
9
+
10
+ ### Added
11
+
12
+ - Added duplicate-line post-edit detection that warns on newly introduced adjacent identical lines and auto-removes one duplicate when bracket-balance is restored
13
+ - Added a warning when suspicious adjacent duplicates are introduced after edits so users can review potential stale-line issues
14
+
15
+ ### Changed
16
+
17
+ - Changed anchor rebase handling to fail when multiple mutating anchors would need auto-rebase, preventing silent misapplied contiguous block rewrites
18
+
19
+ ### Fixed
20
+
21
+ - Fixed bracket-corruption caused by botched block rewrites by automatically removing a newly introduced duplicate adjacent line when removing it restores the original `{}`, `()`, and `[]` balance and by warning when automatic removal is unsafe
22
+
23
+ ## [14.5.4] - 2026-04-28
24
+ ### Breaking Changes
25
+
26
+ - Changed the `atom` edit mode from JSON `{ path, edits }` calls to the compact file-oriented `input` patch language that was previously exposed as `atomd`; `atomd` is no longer a separate edit variant
27
+ - Renamed MCP tool identifiers from the `mcp_<server>_<tool>` format to `mcp__<server>_<tool>` so custom tool names, active tool lists, and persisted MCP selections must be updated to the new prefix
28
+ - Renamed the built-in content-search tool from `grep` to `search`, including SDK/tool event names and settings keys (`search.enabled`, `search.contextBefore`, `search.contextAfter`), so integrations using `grep` and `grep.*` references must be updated
29
+
30
+ ### Added
31
+
32
+ - Added internal URL support to the `search` tool, allowing `artifact://`-style paths that resolve to local files to be searched directly
33
+ - Added IRC relay observation in the main agent UI so every IRC exchange between agents is rendered in the main transcript, even when the main agent is not a direct participant
34
+ - Added stateful `href`/`hrefr` prompt helpers that can reuse anchors remembered from prior `hline` helper calls
35
+
36
+ ### Changed
37
+
38
+ - Changed file-path rendering across search, find, AST, LSP, and related edit outputs to display targets as cwd-relative paths when they resolve inside the working directory and keep absolute paths for files outside the cwd
39
+ - Changed system prompt guidance so in-cwd tool paths must be passed as cwd-relative paths and absolute paths only for out-of-cwd targets or `~` expansion
40
+ - Updated `edit` streaming diff previews for `patch`, `replace`, and `hashline` to produce a single request-level preview for the new single-file `path` mode
41
+ - Bumped default `read.defaultLimit` from 300 to 500 lines, and scaled the read tool's byte budget with the line limit (`max(50KB, lines * 512)`) so the configured line count is no longer truncated by the shared 50KB cap
42
+
43
+ ### Fixed
44
+
45
+ - Fixed atom edit streaming previews to use atom headers for file names instead of apply_patch parsing errors.
46
+ - Fixed collapsed search result rendering so summary and truncation rows stay within the collapsed output budget
47
+ - Updated search path handling to support path lists and internal file paths while preserving previous search behavior
48
+
49
+ ## [14.5.3] - 2026-04-27
50
+
51
+ ### Added
52
+
53
+ - Added bracketed `loc` forms `(anchor)`, `[anchor]`, `[anchor`, `(anchor`, `anchor]`, and `anchor)` to `atom` `splice` editing so a single anchor can target a block body, whole node, or partial node region
54
+ - Added automatic block-delimiter inference for block splices using file extension, defaulting to `{` and using `(` for Lisp-family files
55
+ - Added optional `pre`/`post` arguments to the `href` prompt helper so hashline references can be wrapped as bracketed or parenthesized anchors
56
+ - Added destination-aware indent handling for block replacements by detecting file indent style and reapplying tabs/spaces to spliced body text
57
+
58
+ ### Changed
59
+
60
+ - Changed bracketed atom locators to be `splice`-only and reject `pre`, `post`, or `sed` on region locators
61
+ - Changed `applyAtomEdits` to forbid mixing `splice_block` with other anchor-scoped edit verbs in one call
62
+ - Changed `splice_block` resolution behavior to include selected block range and enclosing-count context in warning output
63
+ - Changed balanced-block parsing to support `kind` selection (`{`, `(`, `[`), nesting depth, and safer same-line enclosing selection
64
+
65
+ ### Removed
66
+
67
+ - Removed the `sed` `F` option for literal matching; `sed` now accepts only `pat`, `rep`, and optional `g`, with `F`-style literal matching no longer supported
68
+
69
+ ### Fixed
70
+
71
+ - Fixed `splice_block` multi-line replacements to replace the exact target region and avoid duplicate braces or duplicated signature lines from bare-anchor `splice` attempts
72
+ - Fixed false-positive “unbalanced” replacement-body warnings caused by braces in regex/string/comment text by skipping those constructs during block scanning
73
+ - Fixed `splice_block` for same-line `(` bodies so inline call sites like `int(port)` can be replaced correctly
74
+
5
75
  ## [14.5.2] - 2026-04-26
6
76
  ### Breaking Changes
7
77
 
@@ -22,7 +22,7 @@ import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
22
22
  import { Key } from "@oh-my-pi/pi-tui";
23
23
 
24
24
  // Read-only tools for plan mode
25
- const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls"];
25
+ const PLAN_MODE_TOOLS = ["read", "bash", "search", "find"];
26
26
 
27
27
  // Full set of tools for normal mode
28
28
  const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
@@ -69,7 +69,7 @@ const { session } = await createAgentSession({
69
69
  });
70
70
 
71
71
  // Read-only tools
72
- const { session } = await createAgentSession({ toolNames: ["read", "grep", "find", "ls"], authStorage, modelRegistry });
72
+ const { session } = await createAgentSession({ toolNames: ["read", "search", "find"], authStorage, modelRegistry });
73
73
 
74
74
  // In-memory
75
75
  const { session } = await createAgentSession({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "14.5.2",
4
+ "version": "14.5.5",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.20.0",
48
48
  "@mozilla/readability": "^0.6.0",
49
- "@oh-my-pi/omp-stats": "14.5.2",
50
- "@oh-my-pi/pi-agent-core": "14.5.2",
51
- "@oh-my-pi/pi-ai": "14.5.2",
52
- "@oh-my-pi/pi-natives": "14.5.2",
53
- "@oh-my-pi/pi-tui": "14.5.2",
54
- "@oh-my-pi/pi-utils": "14.5.2",
49
+ "@oh-my-pi/omp-stats": "14.5.5",
50
+ "@oh-my-pi/pi-agent-core": "14.5.5",
51
+ "@oh-my-pi/pi-ai": "14.5.5",
52
+ "@oh-my-pi/pi-natives": "14.5.5",
53
+ "@oh-my-pi/pi-tui": "14.5.5",
54
+ "@oh-my-pi/pi-utils": "14.5.5",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
57
  "@xterm/headless": "^6.0.0",
@@ -43,21 +43,119 @@ function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; t
43
43
  return { num, text, ref };
44
44
  }
45
45
 
46
+ interface HashlineHelperRef {
47
+ line: number;
48
+ ref: string;
49
+ }
50
+
51
+ interface HashlineHelperState {
52
+ last?: HashlineHelperRef;
53
+ byLine: Map<number, HashlineHelperRef>;
54
+ }
55
+
56
+ const HASHLINE_HELPER_STATE = Symbol("hashlineHelperState");
57
+
58
+ interface HashlineHelperStateHolder {
59
+ [HASHLINE_HELPER_STATE]?: HashlineHelperState;
60
+ }
61
+
62
+ function isHelperOptions(value: unknown): value is prompt.HelperOptions {
63
+ return typeof value === "object" && value !== null && "hash" in value;
64
+ }
65
+
66
+ function splitHelperArgs(args: unknown[]): { positional: unknown[]; options?: prompt.HelperOptions } {
67
+ const maybeOptions = args.at(-1);
68
+ if (!isHelperOptions(maybeOptions)) return { positional: args };
69
+ return { positional: args.slice(0, -1), options: maybeOptions };
70
+ }
71
+
72
+ function getHashlineHelperState(context: unknown, options: prompt.HelperOptions | undefined): HashlineHelperState {
73
+ const data = options?.data;
74
+ const root = data?.root;
75
+ const holderTarget = data && typeof data === "object" ? data : root && typeof root === "object" ? root : context;
76
+ if (!holderTarget || typeof holderTarget !== "object") {
77
+ throw new Error("hashline prompt helpers require an object render context");
78
+ }
79
+
80
+ const holder = holderTarget as HashlineHelperStateHolder;
81
+ if (!holder[HASHLINE_HELPER_STATE]) {
82
+ holder[HASHLINE_HELPER_STATE] = { byLine: new Map() };
83
+ }
84
+ return holder[HASHLINE_HELPER_STATE];
85
+ }
86
+
87
+ function isLineNumberArg(value: unknown): boolean {
88
+ const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
89
+ return Number.isFinite(num);
90
+ }
91
+
92
+ function rememberHashlineRef(state: HashlineHelperState, line: number, ref: string): void {
93
+ const entry = { line, ref };
94
+ state.last = entry;
95
+ state.byLine.set(line, entry);
96
+ }
97
+
98
+ function requireStoredHashlineRef(state: HashlineHelperState, lineArg?: unknown): string {
99
+ if (lineArg === undefined) {
100
+ if (!state.last) {
101
+ throw new Error("{{href}} requires a previous {{hline}} call in the same prompt render");
102
+ }
103
+ return state.last.ref;
104
+ }
105
+
106
+ const line = typeof lineArg === "number" ? lineArg : Number.parseInt(String(lineArg), 10);
107
+ const entry = state.byLine.get(line);
108
+ if (!entry) {
109
+ throw new Error(`{{href ${line}}} requires a previous {{hline ${line} ...}} call in the same prompt render`);
110
+ }
111
+ return entry.ref;
112
+ }
113
+
114
+ function wrapHashlineRef(ref: string, args: unknown[]): string {
115
+ const preStr = typeof args[0] === "string" ? args[0] : "";
116
+ const postStr = typeof args[1] === "string" ? args[1] : "";
117
+ return `${preStr}${ref}${postStr}`;
118
+ }
119
+
120
+ function resolveHashlineRef(state: HashlineHelperState, args: unknown[]): string {
121
+ if (args.length === 0) return requireStoredHashlineRef(state);
122
+ const [first, second, ...rest] = args;
123
+ if (isLineNumberArg(first)) {
124
+ if (second === undefined) return requireStoredHashlineRef(state, first);
125
+ const { ref } = formatHashlineRef(first, second);
126
+ return wrapHashlineRef(ref, rest);
127
+ }
128
+ return wrapHashlineRef(requireStoredHashlineRef(state), args);
129
+ }
130
+
46
131
  /**
47
132
  * {{href lineNum "content"}} — compute a real hashline ref for prompt examples.
48
- * Returns `"lineNumBIGRAM"` (e.g., `"42nd"`) using the actual hash algorithm.
133
+ * {{href lineNum}} quote the ref remembered by the earlier {{hline lineNum "..."}}
134
+ * {{href}} — quote the ref from the previous {{hline}} call.
135
+ * {{href "[" "]"}} — wrap the previous {{hline}} ref with pre/post chars.
136
+ * Returns `"lineNumBIGRAM"` (e.g., `"42nd"`), or `"[42nd]"` when pre/post are supplied.
49
137
  */
50
- prompt.registerHelper("href", (lineNum: unknown, content: unknown): string => {
51
- const { ref } = formatHashlineRef(lineNum, content);
52
- return JSON.stringify(ref);
138
+ prompt.registerHelper("href", function (this: unknown, ...args: unknown[]): string {
139
+ const { positional, options } = splitHelperArgs(args);
140
+ const state = getHashlineHelperState(this, options);
141
+ return JSON.stringify(resolveHashlineRef(state, positional));
142
+ });
143
+ prompt.registerHelper("hrefr", function (this: unknown, ...args: unknown[]): string {
144
+ const { positional, options } = splitHelperArgs(args);
145
+ const state = getHashlineHelperState(this, options);
146
+ return resolveHashlineRef(state, positional);
53
147
  });
54
148
 
55
149
  /**
56
150
  * {{hline lineNum "content"}} — format a full read-style line with prefix.
57
151
  * Returns `"lineNumBIGRAM|content"` (pipe between anchor and content).
58
152
  */
59
- prompt.registerHelper("hline", (lineNum: unknown, content: unknown): string => {
60
- const { ref, text } = formatHashlineRef(lineNum, content);
153
+ prompt.registerHelper("hline", function (this: unknown, ...args: unknown[]): string {
154
+ const { positional, options } = splitHelperArgs(args);
155
+ const [lineNum, content] = positional;
156
+ const { num, ref, text } = formatHashlineRef(lineNum, content);
157
+ const state = getHashlineHelperState(this, options);
158
+ rememberHashlineRef(state, num, ref);
61
159
  return `${ref}${HASHLINE_CONTENT_SEPARATOR}${text}`;
62
160
  });
63
161
 
@@ -1,5 +1,6 @@
1
1
  import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
2
2
  import { TASK_SIMPLE_MODES } from "../task/simple-mode";
3
+ import { EDIT_MODES } from "../utils/edit-mode";
3
4
 
4
5
  /** Unified settings schema - single source of truth for all settings.
5
6
  * Unified settings schema - single source of truth for all settings.
@@ -160,8 +161,8 @@ export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
160
161
  },
161
162
  {
162
163
  pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+",
163
- tool: "grep",
164
- message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.",
164
+ tool: "search",
165
+ message: "Use the `search` tool instead of grep/rg. It respects .gitignore and provides structured output.",
165
166
  },
166
167
  {
167
168
  pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)",
@@ -955,12 +956,12 @@ export const SETTINGS_SCHEMA = {
955
956
  // Edit tool
956
957
  "edit.mode": {
957
958
  type: "enum",
958
- values: ["replace", "patch", "hashline", "vim", "apply_patch", "atom"] as const,
959
+ values: EDIT_MODES,
959
960
  default: "hashline",
960
961
  ui: {
961
962
  tab: "editing",
962
963
  label: "Edit Mode",
963
- description: "Select the edit tool variant (replace, patch, hashline, vim, or apply_patch)",
964
+ description: "Select the edit tool variant (replace, patch, hashline, atom, vim, or apply_patch)",
964
965
  },
965
966
  },
966
967
 
@@ -1027,7 +1028,7 @@ export const SETTINGS_SCHEMA = {
1027
1028
 
1028
1029
  "read.defaultLimit": {
1029
1030
  type: "number",
1030
- default: 300,
1031
+ default: 500,
1031
1032
  ui: {
1032
1033
  tab: "editing",
1033
1034
  label: "Default Read Limit",
@@ -1198,30 +1199,30 @@ export const SETTINGS_SCHEMA = {
1198
1199
  ui: { tab: "tools", label: "Find", description: "Enable the find tool for file searching" },
1199
1200
  },
1200
1201
 
1201
- "grep.enabled": {
1202
+ "search.enabled": {
1202
1203
  type: "boolean",
1203
1204
  default: true,
1204
- ui: { tab: "tools", label: "Grep", description: "Enable the grep tool for content searching" },
1205
+ ui: { tab: "tools", label: "Search", description: "Enable the search tool for content searching" },
1205
1206
  },
1206
1207
 
1207
- "grep.contextBefore": {
1208
+ "search.contextBefore": {
1208
1209
  type: "number",
1209
1210
  default: 1,
1210
1211
  ui: {
1211
1212
  tab: "tools",
1212
- label: "Grep Context Before",
1213
- description: "Lines of context before each grep match",
1213
+ label: "Search Context Before",
1214
+ description: "Lines of context before each search match",
1214
1215
  submenu: true,
1215
1216
  },
1216
1217
  },
1217
1218
 
1218
- "grep.contextAfter": {
1219
+ "search.contextAfter": {
1219
1220
  type: "number",
1220
1221
  default: 3,
1221
1222
  ui: {
1222
1223
  tab: "tools",
1223
- label: "Grep Context After",
1224
- description: "Lines of context after each grep match",
1224
+ label: "Search Context After",
1225
+ description: "Lines of context after each search match",
1225
1226
  submenu: true,
1226
1227
  },
1227
1228
  },
@@ -326,7 +326,7 @@ export class Settings {
326
326
 
327
327
  /**
328
328
  * Get the edit variant for a specific model.
329
- * Returns "patch", "replace", "hashline", "vim", "apply_patch", or null (use global default).
329
+ * Returns "patch", "replace", "hashline", "atom", "vim", "apply_patch", or null (use global default).
330
330
  */
331
331
  getEditVariantForModel(model: string | undefined): EditMode | null {
332
332
  if (!model) return null;
package/src/cursor.ts CHANGED
@@ -177,10 +177,10 @@ export class CursorExecHandlers implements ICursorExecHandlers {
177
177
 
178
178
  async grep(args: Parameters<NonNullable<ICursorExecHandlers["grep"]>>[0]) {
179
179
  const toolCallId = decodeToolCallId(args.toolCallId);
180
- const grepPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || ".";
181
- const toolResultMessage = await executeTool(this.options, "grep", toolCallId, {
180
+ const searchPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || ".";
181
+ const toolResultMessage = await executeTool(this.options, "search", toolCallId, {
182
182
  pattern: args.pattern,
183
- path: grepPath,
183
+ path: searchPath,
184
184
  i: args.caseInsensitive || undefined,
185
185
  });
186
186
  return toolResultMessage;
@@ -327,7 +327,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
327
327
  const toolCallId = decodeToolCallId(call.toolCallId);
328
328
  const tool = this.options.tools.get(toolName);
329
329
  if (!tool) {
330
- const availableTools = Array.from(this.options.tools.keys()).filter(name => name.startsWith("mcp_"));
330
+ const availableTools = Array.from(this.options.tools.keys()).filter(name => name.startsWith("mcp__"));
331
331
  const message = formatMcpToolErrorMessage(toolName, availableTools);
332
332
  const result = buildToolErrorResult(message);
333
333
  return createToolResultMessage(toolCallId, toolName, result, true);