@oh-my-pi/pi-coding-agent 14.5.8 → 14.5.10

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 (58) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/package.json +7 -15
  3. package/scripts/build-binary.ts +1 -1
  4. package/src/cli/update-cli.ts +25 -1
  5. package/src/config/model-registry.ts +21 -19
  6. package/src/config/settings-schema.ts +14 -19
  7. package/src/discovery/claude-plugins.ts +28 -3
  8. package/src/edit/modes/atom.lark +7 -5
  9. package/src/edit/modes/atom.ts +510 -73
  10. package/src/edit/modes/hashline.ts +172 -91
  11. package/src/extensibility/extensions/runner.ts +34 -1
  12. package/src/extensibility/extensions/types.ts +8 -0
  13. package/src/lsp/client.ts +27 -35
  14. package/src/lsp/index.ts +2 -4
  15. package/src/lsp/render.ts +0 -3
  16. package/src/lsp/types.ts +1 -4
  17. package/src/lsp/utils.ts +18 -14
  18. package/src/memories/index.ts +5 -0
  19. package/src/modes/components/settings-defs.ts +1 -1
  20. package/src/modes/controllers/command-controller.ts +17 -0
  21. package/src/modes/controllers/input-controller.ts +7 -1
  22. package/src/modes/controllers/selector-controller.ts +2 -2
  23. package/src/modes/interactive-mode.ts +57 -26
  24. package/src/modes/theme/theme.ts +10 -1
  25. package/src/modes/types.ts +5 -3
  26. package/src/modes/utils/context-usage.ts +294 -0
  27. package/src/modes/utils/ui-helpers.ts +19 -6
  28. package/src/prompts/system/auto-continue.md +1 -0
  29. package/src/prompts/tools/atom.md +99 -44
  30. package/src/prompts/tools/exit-plan-mode.md +5 -39
  31. package/src/prompts/tools/github.md +3 -3
  32. package/src/prompts/tools/lsp.md +2 -3
  33. package/src/prompts/tools/{run-command.md → recipe.md} +1 -1
  34. package/src/prompts/tools/task.md +34 -147
  35. package/src/prompts/tools/todo-write.md +22 -64
  36. package/src/sdk.ts +13 -2
  37. package/src/session/agent-session.ts +175 -79
  38. package/src/session/compaction/compaction.ts +35 -22
  39. package/src/session/session-dump-format.ts +1 -0
  40. package/src/session/session-manager.ts +19 -2
  41. package/src/slash-commands/builtin-registry.ts +12 -5
  42. package/src/tools/bash.ts +9 -4
  43. package/src/tools/debug.ts +57 -70
  44. package/src/tools/gh.ts +267 -119
  45. package/src/tools/index.ts +7 -7
  46. package/src/tools/{run-command → recipe}/index.ts +19 -19
  47. package/src/tools/recipe/render.ts +19 -0
  48. package/src/tools/{run-command → recipe}/runner.ts +28 -7
  49. package/src/tools/{run-command → recipe}/runners/pkg.ts +23 -53
  50. package/src/tools/renderers.ts +2 -2
  51. package/src/utils/git.ts +61 -2
  52. package/src/web/search/providers/searxng.ts +71 -13
  53. package/src/tools/run-command/render.ts +0 -18
  54. /package/src/tools/{run-command → recipe}/runners/cargo.ts +0 -0
  55. /package/src/tools/{run-command → recipe}/runners/index.ts +0 -0
  56. /package/src/tools/{run-command → recipe}/runners/just.ts +0 -0
  57. /package/src/tools/{run-command → recipe}/runners/make.ts +0 -0
  58. /package/src/tools/{run-command → recipe}/runners/task.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,61 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.5.10] - 2026-04-30
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed the `worktree` parameter from `github` `pr_checkout`. Worktrees are now always written to `~/.omp/wt/<encoded-primary-repo>/pr-<number>/`, derived from the primary repository path
10
+ - Stopped reading the `branch` parameter for `github` `pr_checkout`. The local branch is now always `pr-<number>`; the `branch` schema field is still accepted by `pr_push`, `repo_view`, and `run_watch`
11
+
12
+ ### Added
13
+
14
+ - Added `checkouts` summary entries to `pr_checkout` results, including each checkout's branch, worktree path, remote, and reuse status
15
+ - Added combined summaries for `pr_view` and `pr_diff` when `pr` is an array, so multi-request responses now include all requested pull requests in one return
16
+ - Added array support to the `pr` parameter on `github` `pr_view`, `pr_diff`, and `pr_checkout` so a single call can fetch, diff, or check out multiple pull requests in one batch
17
+ - Added a per-repo serialization lock (`withRepoLock`) so concurrent `pr_checkout` calls against the same repository no longer race on git's internal `.git/config.lock`, commit-graph, and worktree lock files
18
+
19
+ ### Changed
20
+
21
+ - Changed the diff preview shown after edits so changed lines are never collapsed: removed runs and the global preview budget no longer truncate, only unchanged context still collapses
22
+ - Changed adjacent `-`/`+` pairs in edit previews to fold into a single `*<line><hash>|<new-content>` modification line so 1:1 line replacements stay compact
23
+ - Changed `git.remote.add` to be idempotent when the remote already exists with the same URL (instead of failing with `remote ... already exists`), and to surface a clear error when the existing URL differs
24
+ - Changed `pr_checkout` to run `gh pr view` calls in parallel for batch invocations while serializing the in-repo git mutations to keep the operation race-free
25
+ - Changed `pr_checkout` to auto-derive the worktree location and local branch name (see Breaking Changes), removing the per-call overrides that previously let callers pin a worktree path or local branch
26
+
27
+ ### Removed
28
+
29
+ - Removed the `./hooks` and `./hooks/*` package export entries
30
+ - Removed the `Suspicious duplicate` warning emitted after edits — it produced too many false positives (e.g. legitimate adjacent `\t});\n\t});`); the auto-fix path that uses bracket balance to safely de-duplicate is unchanged
31
+
32
+ ### Fixed
33
+
34
+ - Fixed bash interceptor rules to also check the original command before `cd` normalization, so leading `cd ... &&` wrappers no longer bypass interception
35
+ - Fixed LSP client shutdown to properly await the language server's exit instead of fire-and-forget, preventing premature process termination on SIGINT and SIGTERM
36
+ - Fixed concurrent bash commands being tracked independently so aborting one no longer silently drops tracking of others
37
+
38
+ ## [14.5.9] - 2026-04-30
39
+
40
+ ### Added
41
+
42
+ - Added the `/context` slash command to display an estimated context-usage breakdown panel for the current session
43
+ - Added `-LidA..LidB` syntax to delete inclusive line ranges in a single atom operation
44
+ - Added `LidA..LidB=TEXT` range-replace syntax with `\TEXT` and `\` continuation lines for multi-line replacement blocks
45
+ - Added shorthand cursor+insert operations in atom edits, including `^Lid` (insert before anchor), `^+TEXT`, `$+TEXT`, and `Lid+TEXT`/`@Lid+TEXT`
46
+ - Added standalone file-op fallback so `!rm` and `!mv DEST` inputs can be normalized into sections when using split input parsing
47
+
48
+ ### Changed
49
+
50
+ - Changed token counting to use tokenizer-based estimates instead of a character-per-4 heuristic for context and compaction calculations
51
+ - Changed hashline anchor auto-rebase tolerance from ±2 lines to ±5 lines for stale Lid recovery
52
+ - Changed atom input handling so `#`-prefixed lines are treated as comments and ignored
53
+ - Changed execution when all edits are no-op `Lid=TEXT` replacements to return success with a no-change explanation instead of throwing
54
+
55
+ ### Fixed
56
+
57
+ - Fixed malformed range and unified-diff-like atom syntax by rejecting reversed ranges, mismatched range endpoint hashes, and forms like `+Lid|TEXT`, `+Lid=TEXT`, and `-LidA..LidB|TEXT` with explicit actionable errors
58
+ - Fixed hash mismatch errors to include likely-shifted anchor hints when a unique matching line is found elsewhere in the file
59
+
5
60
  ## [14.5.8] - 2026-04-29
6
61
  ### Breaking Changes
7
62
 
@@ -60,6 +115,7 @@
60
115
 
61
116
  ### Added
62
117
 
118
+ - Added the `after_provider_response` extension event for observing provider response status, headers, and request IDs.
63
119
  - Added internal URL support to the `search` tool, allowing `artifact://`-style paths that resolve to local files to be searched directly
64
120
  - 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
65
121
  - Added stateful `href`/`hrefr` prompt helpers that can reuse anchors remembered from prior `hline` helper calls
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.8",
4
+ "version": "14.5.10",
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.8",
50
- "@oh-my-pi/pi-agent-core": "14.5.8",
51
- "@oh-my-pi/pi-ai": "14.5.8",
52
- "@oh-my-pi/pi-natives": "14.5.8",
53
- "@oh-my-pi/pi-tui": "14.5.8",
54
- "@oh-my-pi/pi-utils": "14.5.8",
49
+ "@oh-my-pi/omp-stats": "14.5.10",
50
+ "@oh-my-pi/pi-agent-core": "14.5.10",
51
+ "@oh-my-pi/pi-ai": "14.5.10",
52
+ "@oh-my-pi/pi-natives": "14.5.10",
53
+ "@oh-my-pi/pi-tui": "14.5.10",
54
+ "@oh-my-pi/pi-utils": "14.5.10",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
57
  "@xterm/headless": "^6.0.0",
@@ -516,14 +516,6 @@
516
516
  "types": "./src/web/search/providers/*.ts",
517
517
  "import": "./src/web/search/providers/*.ts"
518
518
  },
519
- "./hooks": {
520
- "types": "./src/extensibility/hooks/index.ts",
521
- "import": "./src/extensibility/hooks/index.ts"
522
- },
523
- "./hooks/*": {
524
- "types": "./src/extensibility/hooks/*.ts",
525
- "import": "./src/extensibility/hooks/*.ts"
526
- },
527
519
  "./*.js": "./src/*.ts"
528
520
  }
529
521
  }
@@ -34,7 +34,7 @@ async function main(): Promise<void> {
34
34
  "build",
35
35
  "--compile",
36
36
  "--define",
37
- "PI_COMPILED=true",
37
+ 'process.env.PI_COMPILED="true"',
38
38
  "--external",
39
39
  "mupdf",
40
40
  "--root",
@@ -53,13 +53,37 @@ function normalizePathForComparison(filePath: string): string {
53
53
  return normalized;
54
54
  }
55
55
 
56
- function isPathInDirectory(filePath: string, directoryPath: string): boolean {
56
+ function tryRealpath(p: string): string | undefined {
57
+ try {
58
+ return fs.realpathSync.native(p);
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ }
63
+
64
+ function isPathInDirectoryLexical(filePath: string, directoryPath: string): boolean {
57
65
  const normalizedPath = normalizePathForComparison(path.resolve(filePath));
58
66
  const normalizedDirectory = normalizePathForComparison(path.resolve(directoryPath));
59
67
  const relativePath = path.relative(normalizedDirectory, normalizedPath);
60
68
  return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
61
69
  }
62
70
 
71
+ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
72
+ if (isPathInDirectoryLexical(filePath, directoryPath)) return true;
73
+ // Layer realpath resolution on top of the lexical guard. On Windows, ~/.bun
74
+ // is a junction when Bun is installed via Scoop, so `bun pm bin -g` and the
75
+ // PATH-resolved omp path can refer to the same directory through different
76
+ // strings. path.resolve does not traverse junctions/symlinks; realpath does.
77
+ // Resolve the file's parent directory to tolerate the file itself not yet
78
+ // existing (e.g. a fresh install path) while still catching link-traversed
79
+ // equality once the directory exists.
80
+ const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
81
+ const dirReal = tryRealpath(path.resolve(directoryPath));
82
+ if (!fileDir || !dirReal) return false;
83
+ const resolvedFile = path.join(fileDir, path.basename(filePath));
84
+ return isPathInDirectoryLexical(resolvedFile, dirReal);
85
+ }
86
+
63
87
  type UpdateTarget = { method: "bun" } | { method: "binary"; path: string };
64
88
 
65
89
  function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
@@ -145,8 +145,16 @@ const OpenAICompatSchema = Type.Object({
145
145
  maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
146
146
  supportsUsageInStreaming: Type.Optional(Type.Boolean()),
147
147
  requiresToolResultName: Type.Optional(Type.Boolean()),
148
+ requiresMistralToolIds: Type.Optional(Type.Boolean()),
148
149
  requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
149
150
  requiresThinkingAsText: Type.Optional(Type.Boolean()),
151
+ reasoningContentField: Type.Optional(
152
+ Type.Union([Type.Literal("reasoning_content"), Type.Literal("reasoning"), Type.Literal("reasoning_text")]),
153
+ ),
154
+ requiresReasoningContentForToolCalls: Type.Optional(Type.Boolean()),
155
+ requiresAssistantContentForToolCalls: Type.Optional(Type.Boolean()),
156
+ supportsToolChoice: Type.Optional(Type.Boolean()),
157
+ disableReasoningOnForcedToolChoice: Type.Optional(Type.Boolean()),
150
158
  thinkingFormat: Type.Optional(
151
159
  Type.Union([
152
160
  Type.Literal("openai"),
@@ -183,6 +191,7 @@ const ModelThinkingSchema = Type.Object({
183
191
  minLevel: EffortSchema,
184
192
  maxLevel: EffortSchema,
185
193
  mode: ThinkingControlModeSchema,
194
+ defaultLevel: Type.Optional(EffortSchema),
186
195
  });
187
196
 
188
197
  // Schema for custom model definition
@@ -558,27 +567,20 @@ function resolveOAuthAccountIdForAccessToken(
558
567
  return undefined;
559
568
  }
560
569
 
561
- function mergeCompat(
562
- baseCompat: Model<Api>["compat"],
563
- overrideCompat: ModelOverride["compat"],
564
- ): Model<Api>["compat"] | undefined {
570
+ function mergeCompat<TBase extends object, TOverride extends object>(
571
+ baseCompat: TBase | null | undefined,
572
+ overrideCompat: TOverride | null | undefined,
573
+ ): (TBase & TOverride) | TBase | TOverride | undefined {
574
+ if (!baseCompat) return overrideCompat ?? undefined;
565
575
  if (!overrideCompat) return baseCompat;
566
- const base = baseCompat ?? {};
567
- const override = overrideCompat;
568
- const merged: NonNullable<Model<Api>["compat"]> = { ...base, ...override };
569
- if (baseCompat?.reasoningEffortMap || overrideCompat.reasoningEffortMap) {
570
- merged.reasoningEffortMap = { ...baseCompat?.reasoningEffortMap, ...overrideCompat.reasoningEffortMap };
571
- }
572
- if (baseCompat?.openRouterRouting || overrideCompat.openRouterRouting) {
573
- merged.openRouterRouting = { ...baseCompat?.openRouterRouting, ...overrideCompat.openRouterRouting };
574
- }
575
- if (baseCompat?.vercelGatewayRouting || overrideCompat.vercelGatewayRouting) {
576
- merged.vercelGatewayRouting = { ...baseCompat?.vercelGatewayRouting, ...overrideCompat.vercelGatewayRouting };
577
- }
578
- if (baseCompat?.extraBody || overrideCompat.extraBody) {
579
- merged.extraBody = { ...baseCompat?.extraBody, ...overrideCompat.extraBody };
576
+
577
+ const merged: Record<string, unknown> = { ...(baseCompat as Record<string, unknown>) };
578
+ for (const [key, overrideValue] of Object.entries(overrideCompat)) {
579
+ const baseValue = (baseCompat as Record<string, unknown>)[key];
580
+ merged[key] =
581
+ isRecord(baseValue) && isRecord(overrideValue) ? mergeCompat(baseValue, overrideValue) : overrideValue;
580
582
  }
581
- return merged;
583
+ return merged as TBase & TOverride;
582
584
  }
583
585
 
584
586
  function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
@@ -1306,14 +1306,14 @@ export const SETTINGS_SCHEMA = {
1306
1306
  },
1307
1307
  },
1308
1308
 
1309
- "runCommand.enabled": {
1309
+ "recipe.enabled": {
1310
1310
  type: "boolean",
1311
1311
  default: true,
1312
1312
  ui: {
1313
1313
  tab: "tools",
1314
- label: "Run command",
1314
+ label: "Recipe",
1315
1315
  description:
1316
- "Enable the run_command tool when a justfile / package.json / Cargo.toml / Makefile / Taskfile is present",
1316
+ "Enable the recipe tool when a justfile / package.json / Cargo.toml / Makefile / Taskfile is present",
1317
1317
  },
1318
1318
  },
1319
1319
 
@@ -1793,38 +1793,33 @@ export const SETTINGS_SCHEMA = {
1793
1793
  ui: {
1794
1794
  tab: "providers",
1795
1795
  label: "SearXNG Endpoint",
1796
- description: "Base URL of the SearXNG instance (e.g. https://searx.example.org)",
1796
+ description: "Self-hosted search base URL",
1797
1797
  },
1798
1798
  },
1799
1799
 
1800
1800
  "searxng.token": {
1801
1801
  type: "string",
1802
1802
  default: undefined,
1803
- ui: {
1804
- tab: "providers",
1805
- label: "SearXNG Token",
1806
- description: "Optional bearer token for SearXNG authentication",
1807
- },
1803
+ },
1804
+
1805
+ "searxng.basicUsername": {
1806
+ type: "string",
1807
+ default: undefined,
1808
+ },
1809
+
1810
+ "searxng.basicPassword": {
1811
+ type: "string",
1812
+ default: undefined,
1808
1813
  },
1809
1814
 
1810
1815
  "searxng.categories": {
1811
1816
  type: "string",
1812
1817
  default: undefined,
1813
- ui: {
1814
- tab: "providers",
1815
- label: "SearXNG Categories",
1816
- description: "Comma-separated categories filter (e.g. general,news,science)",
1817
- },
1818
1818
  },
1819
1819
 
1820
1820
  "searxng.language": {
1821
1821
  type: "string",
1822
1822
  default: undefined,
1823
- ui: {
1824
- tab: "providers",
1825
- label: "SearXNG Language",
1826
- description: "Language code for search results (e.g. en, zh-CN)",
1827
- },
1828
1823
  },
1829
1824
 
1830
1825
  "commit.mapReduceEnabled": { type: "boolean", default: true },
@@ -265,10 +265,28 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
265
265
  }
266
266
 
267
267
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
268
- const config = parsed as { mcpServers?: Record<string, unknown> };
269
- if (!config.mcpServers || typeof config.mcpServers !== "object") continue;
268
+ const obj = parsed as Record<string, unknown>;
269
+
270
+ // Two shapes are supported:
271
+ // nested: { "mcpServers": { name: cfg, ... } } (OMP/Claude Code project shape)
272
+ // flat: { name: cfg, ... } (Claude marketplace plugin shape)
273
+ // If "mcpServers" is present and an object, treat it as the canonical map.
274
+ // Otherwise, treat the whole object as the server map.
275
+ let servers: Record<string, unknown>;
276
+ if (
277
+ obj.mcpServers !== undefined &&
278
+ obj.mcpServers !== null &&
279
+ typeof obj.mcpServers === "object" &&
280
+ !Array.isArray(obj.mcpServers)
281
+ ) {
282
+ servers = obj.mcpServers as Record<string, unknown>;
283
+ } else if (!("mcpServers" in obj)) {
284
+ servers = obj;
285
+ } else {
286
+ continue;
287
+ }
270
288
 
271
- for (const [serverName, serverCfg] of Object.entries(config.mcpServers)) {
289
+ for (const [serverName, serverCfg] of Object.entries(servers)) {
272
290
  if (!serverCfg || typeof serverCfg !== "object" || Array.isArray(serverCfg)) continue;
273
291
  const raw = serverCfg as {
274
292
  enabled?: boolean;
@@ -283,6 +301,13 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
283
301
  oauth?: MCPServer["oauth"];
284
302
  type?: string;
285
303
  };
304
+ // Require either command (stdio) or url (HTTP/SSE) — Claude marketplace plugins
305
+ // occasionally ship .mcp.json entries with neither, which would register a useless
306
+ // server and surface as a connection error at runtime.
307
+ if (typeof raw.command !== "string" && typeof raw.url !== "string") {
308
+ warnings.push(`[claude-plugins] Skipping MCP server "${serverName}" in ${mcpPath}: missing command or url`);
309
+ continue;
310
+ }
286
311
  const namespacedName = root.plugin ? `${root.plugin}:${serverName}` : serverName;
287
312
  const server: MCPServer = {
288
313
  name: namespacedName,
@@ -8,8 +8,8 @@ file_header: "---" filename LF
8
8
  filename: /(.+)/
9
9
 
10
10
  line_change: line* mutation_line line*
11
- line: insert_line | delete_line | set_line | move_line | blank
12
- mutation_line: insert_line | delete_line | set_line
11
+ line: insert_line | delete_line | set_block | move_line | blank
12
+ mutation_line: insert_line | delete_line | set_block
13
13
 
14
14
  whole_file_change: blank* whole_file_line blank*
15
15
  whole_file_line: remove_file | move_file
@@ -18,9 +18,11 @@ move_file: "!mv" WS destination LF
18
18
  destination: /(?:[^ \t\r\n]+|"[^"\r\n]+"|'[^'\r\n]+')/
19
19
 
20
20
  insert_line: "+" /(.*)/ LF
21
- delete_line: "-" LID LF
22
- set_line: LID "=" /(.*)/ LF
23
- move_line: ("@" LID | "$" | "^") LF
21
+ delete_line: "-" (LID ".." LID | LID) LF
22
+ set_line: (LID ".." LID | LID) WS? "=" /(.*)/ LF
23
+ set_block: set_line continuation_line*
24
+ continuation_line: "\\" /(.*)/ LF
25
+ move_line: ("@" LID | "^" LID | "^" | "$") LF
24
26
 
25
27
  LID: /[1-9][0-9]*[a-z]{2}/
26
28
  WS: /[ \t]+/