@oh-my-pi/pi-coding-agent 14.9.9 → 15.0.0
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.
- package/CHANGELOG.md +82 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +1 -1
- package/src/cli/args.ts +2 -2
- package/src/cli.ts +1 -0
- package/src/commands/acp.ts +24 -0
- package/src/commands/launch.ts +6 -4
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/config/model-resolver.ts +30 -0
- package/src/config/settings-schema.ts +31 -0
- package/src/edit/index.ts +22 -1
- package/src/edit/modes/patch.ts +10 -0
- package/src/edit/modes/replace.ts +3 -0
- package/src/edit/renderer.ts +10 -0
- package/src/eval/js/context-manager.ts +1 -1
- package/src/eval/js/shared/rewrite-imports.ts +120 -48
- package/src/eval/js/shared/runtime.ts +31 -4
- package/src/eval/js/tool-bridge.ts +43 -21
- package/src/extensibility/extensions/runner.ts +54 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +33 -1
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/router.ts +6 -3
- package/src/internal-urls/types.ts +22 -1
- package/src/main.ts +13 -9
- package/src/modes/acp/acp-agent.ts +361 -54
- package/src/modes/acp/acp-client-bridge.ts +152 -0
- package/src/modes/acp/acp-event-mapper.ts +180 -15
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/read-tool-group.ts +29 -1
- package/src/modes/controllers/command-controller.ts +14 -6
- package/src/modes/controllers/event-controller.ts +24 -11
- package/src/modes/controllers/extension-ui-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +72 -39
- package/src/modes/interactive-mode.ts +71 -7
- package/src/modes/rpc/rpc-mode.ts +17 -2
- package/src/modes/types.ts +6 -2
- package/src/modes/utils/ui-helpers.ts +15 -3
- package/src/prompts/agents/designer.md +5 -5
- package/src/prompts/agents/explore.md +7 -7
- package/src/prompts/agents/init.md +9 -9
- package/src/prompts/agents/librarian.md +14 -14
- package/src/prompts/agents/plan.md +4 -4
- package/src/prompts/agents/reviewer.md +5 -5
- package/src/prompts/agents/task.md +10 -10
- package/src/prompts/commands/orchestrate.md +2 -2
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/memories/consolidation.md +2 -2
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +2 -2
- package/src/prompts/system/eager-todo.md +6 -6
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +22 -21
- package/src/prompts/system/plan-mode-approved.md +4 -4
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +2 -2
- package/src/prompts/system/project-prompt.md +4 -4
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/subagent-yield-reminder.md +4 -4
- package/src/prompts/system/system-prompt.md +72 -71
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/tools/apply-patch.md +1 -1
- package/src/prompts/tools/ast-edit.md +3 -3
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/checkpoint.md +3 -3
- package/src/prompts/tools/exit-plan-mode.md +2 -2
- package/src/prompts/tools/find.md +3 -3
- package/src/prompts/tools/github.md +2 -5
- package/src/prompts/tools/hashline.md +6 -6
- package/src/prompts/tools/image-gen.md +3 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +6 -6
- package/src/prompts/tools/read.md +7 -7
- package/src/prompts/tools/replace.md +5 -5
- package/src/prompts/tools/retain.md +1 -1
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search.md +2 -2
- package/src/prompts/tools/ssh.md +2 -2
- package/src/prompts/tools/task.md +12 -6
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +69 -12
- package/src/session/agent-session.ts +231 -22
- package/src/session/client-bridge.ts +81 -0
- package/src/session/compaction/errors.ts +31 -0
- package/src/session/compaction/index.ts +1 -0
- package/src/slash-commands/acp-builtins.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +699 -116
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +23 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +193 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +91 -0
- package/src/slash-commands/types.ts +126 -0
- package/src/task/executor.ts +10 -3
- package/src/task/index.ts +17 -1
- package/src/task/render.ts +6 -3
- package/src/tools/bash.ts +176 -2
- package/src/tools/conflict-detect.ts +6 -6
- package/src/tools/fetch.ts +15 -4
- package/src/tools/find.ts +19 -1
- package/src/tools/gh-renderer.ts +0 -12
- package/src/tools/gh.ts +682 -176
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/read.ts +110 -27
- package/src/tools/write.ts +23 -1
- package/src/tui/code-cell.ts +70 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.0.0] - 2026-05-13
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Removed `op: issue_view` and `op: pr_view` from the `github` tool. Read single issues/PRs via the `read` tool against `issue://<N>` / `pr://<N>` (or the long form `issue://<owner>/<repo>/<N>` / `pr://<owner>/<repo>/<N>`); append `?comments=0` to drop the comments section. The `issue` and `comments` parameters were removed from the tool schema since no remaining op consumes them. Mutating ops (`pr_create`, `pr_checkout`, `pr_push`), `repo_view`, `search_*`, and `run_watch` are unchanged.
|
|
9
|
+
- Removed `op: pr_diff` (along with the `nameOnly` and `exclude` schema fields) from the `github` tool. Read PR diffs through the new `pr://` URL family: `pr://<N>/diff` for the changed-file listing, `pr://<N>/diff/<i>` for a single file slice (1-indexed), and `pr://<N>/diff/all` for the verbatim unified diff. Long-form `pr://<owner>/<repo>/<N>/diff[/…]` works the same way. All three variants share one `gh pr diff` invocation through a new `pr-diff` cache row, so the listing and per-file slices reconstruct from cached bytes without re-shelling. Diff content is served as `text/plain` so the `read` tool's line selectors (e.g. `pr://<N>/diff/all:200-400`) page the cached output without falsely advertising hashline anchors.
|
|
10
|
+
- Renamed ACP custom extension methods from `omp/*` to `_omp/*` to comply with the ACP spec's `_`-prefix requirement for non-spec methods; existing callers must update method names
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added markdown rendering for `read` results when content type is `text/markdown`, so GitHub internal-URL outputs are shown as formatted markdown instead of plain code blocks
|
|
15
|
+
- Added `pr://<N>/diff`, `pr://<N>/diff/<i>`, and `pr://<N>/diff/all` internal-URL shapes covering changed-file listings, per-file slices, and the full unified diff. They share one `pr-diff` SQLite cache row with the same TTL knobs as `pr://<N>` views (`github.cache.softTtlSec` / `github.cache.hardTtlSec` / `github.cache.enabled`). Single PR views now advertise the diff entry point via a `Diff: pr://<owner>/<repo>/<N>/diff` note. Cache schema bumped to `user_version = 3`; older rows are dropped on first open to add credential-scoped keys and relax the `kind` CHECK constraint.
|
|
16
|
+
- Added `issue://` / `pr://` internal-URL schemes that share a SQLite-backed cache with the rest of the `github` tool. Single-item reads (`issue://<N>`, `issue://<owner>/<repo>/<N>`) return rendered markdown and within `github.cache.softTtlSec` (default 5 minutes) skip the `gh` round-trip entirely; within `github.cache.hardTtlSec` (default 7 days) the cached row is returned and a background refresh is scheduled. Root and repo-scoped reads (`issue://`, `pr://owner/repo`) issue a live `gh issue list` / `gh pr list` for browsing, supporting `?state=open|closed|all` for issues, `?state=open|closed|merged|all` for PRs, and `?limit=`, `?author=`, `?label=` query params. Rendered output lands in `~/.omp/cache/github-cache.db` (override via `OMP_GITHUB_CACHE_DB`); disable the cache entirely with `github.cache.enabled = false`. Cwd→default-repo lookups (`gh repo view`) are memoized per-process.
|
|
17
|
+
- Added new `Approve and compact context` choice to the ExitPlanMode approval selector. Sits between `Approve and execute` (purge session) and `Approve and keep context` (full transcript) — runs `/compact` on the plan-mode transcript with a planning-specific summarization hint, then dispatches the plan-approved execution turn so it lands on a fresh cache anchor with the summarized rationale carried over. Cancelling the compaction (Esc or any other abort source) defers the execution dispatch and surfaces a warning so the operator can resubmit manually; non-abort failures proceed best-effort.
|
|
18
|
+
- Added `CompactionCancelledError` typed sentinel and `CompactionOutcome` (`"ok" | "cancelled" | "failed"`) return type to `@oh-my-pi/pi-coding-agent/session/compaction`. `CommandController.executeCompaction` and `handleCompactCommand` now return the outcome instead of `void` so callers can discriminate user-driven aborts from generic failures without inspecting error messages.
|
|
19
|
+
- Added a `credential_disabled` extension event so extensions can subscribe via `pi.on("credential_disabled", handler)` and react when `AuthStorage` automatically soft-disables a credential (e.g. OAuth `invalid_grant`). Replaces the current `agent_end` errorMessage regex pattern downstream extensions have to match against. Handler payload is `{ type, provider, disabledCause }`. `createAgentSession()` subscribes the per-session extension runner to the shared `AuthStorage` via `authStorage.onCredentialDisabled(...)` at the very top of session creation — before any startup model probes run — so events fire on every disable regardless of whether the embedder also has a constructor `onCredentialDisabled` handler attached. The SDK forwards through `ExtensionRunner.emitCredentialDisabled(event)`, which buffers events until `runner.initialize(...)` runs in the mode controller and then flushes them through `emit()` so extension handlers see populated UI/runtime context (rather than the constructor's no-op default with `hasUI=false`, an unset model, and no-op runtime actions). On `session.dispose()` the subscription is unsubscribed; the embedder's constructor-attached listener keeps firing through its own permanent subscription. The outer `createAgentSession()` catch also releases the subscription if startup throws before the dispose-wrap is wired, so repeated retries don't accumulate dead listeners.
|
|
20
|
+
- Added `omp acp` subcommand for launching as an ACP (Agent Client Protocol) server over stdio
|
|
21
|
+
- Added explicit `type` discriminators to ACP `initialize` auth methods, including a `terminal` setup method gated on `clientCapabilities.auth.terminal`
|
|
22
|
+
- Added ACP equivalents for the remaining TUI slash commands (`/jobs`, `/changelog`, `/dump`, `/copy`, `/hotkeys`, `/extensions`, `/agents`, `/model`, `/plan`, `/loop`, `/btw`, `/login`, `/logout`, `/resume`, `/tree`, `/branch`, `/new`, `/drop`, `/handoff`, `/fork`, `/session delete`, `/export`, `/share`, `/todo`, `/memory`, `/move`, `/mcp`, `/ssh`, `/marketplace`, `/plugins`) so ACP clients reach feature parity with the TUI for non-interactive flows
|
|
23
|
+
- Added ACP `plan` mode: when `plan.enabled` setting is on, ACP `session/new`/`load`/`resume`/`fork` advertise a `plan` mode alongside `default`; `session/set_mode` toggles plan-mode state so the next agent turn injects the plan-mode system prompt
|
|
24
|
+
- Added ACP `ClientBridge` abstraction (`packages/coding-agent/src/session/client-bridge.ts`) that routes tool I/O through the connected client when capabilities are advertised at `initialize`; populated from `AgentSideConnection` in ACP mode
|
|
25
|
+
- Added ACP `terminal/*` routing for `bash`: when the client advertises `terminal: true`, the tool creates a client-side terminal, embeds its `terminalId` on the live tool card, polls output, and releases the handle on exit or abort
|
|
26
|
+
- Added ACP `fs/read_text_file` and `fs/write_text_file` routing for the `read` and `write` tools: when the client advertises `fs.readTextFile` / `fs.writeTextFile`, plain-text reads/writes go through the editor (surfacing unsaved buffer content and letting the editor track agent writes); falls back to disk only for reads, throws on bridge write failures
|
|
27
|
+
- Added ACP `session/request_permission` gate around `bash`, `edit`, `write`, and `ast_edit` when an ACP client is connected; remembers `allow_always` / `reject_always` decisions per tool for the session lifetime
|
|
28
|
+
- Added `diff` `ToolCallContent` emission for edit tool results: per-file `oldText`/`newText` is threaded through `EditToolPerFileResult` / `EditToolDetails` so ACP clients can render inline diffs
|
|
29
|
+
- Added richer ACP `StopReason` mapping (`max_tokens`, `refusal`, `cancelled`) derived from the last assistant message's internal stop reason; previously only `end_turn`/`cancelled` were emitted
|
|
30
|
+
- Added `_meta.messageCount` and `_meta.size` on `session/list` `SessionInfo` entries
|
|
31
|
+
- Added ACP `tool_call_update` `locations` refresh from in-flight tool args and final result details so clients can "follow along" multi-file edits in real time
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Changed issue and pull-request list entries to link to repository-qualified URLs (for example `issue://owner/repo/<N>`) so list items open correctly outside the default repo
|
|
36
|
+
- Aligned prompt instruction language by defining `NEVER` and `AVOID` as strict aliases for `MUST NOT` and `SHOULD NOT` in the system prompt, and standardized agent, tool, and system prompt templates to use those terms consistently
|
|
37
|
+
- Changed `--mode acp` to apply the same stdout-quiet overrides as `--mode rpc` so no banner or status text leaks into the JSON-RPC channel
|
|
38
|
+
- Changed ACP startup to no longer require a configured model so registry validators and clients can complete `initialize` and `authenticate` before any model is selected
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Deferred flushing of buffered `credential_disabled` events during extension runner initialization to a microtask so handler failures are now routed through `onError()` registrations made immediately after `initialize()`, preserving extension error reporting
|
|
42
|
+
- Fixed `eval` tool dynamic `await import("./relative.ts")` calls failing with `Cannot find module ... from .../eval/js/shared/runtime.ts`. The static-import rewriter only handled `ImportDeclaration` nodes, so dynamic-import call expressions resolved their specifier against the worker module's URL instead of the session cwd. The rewriter now walks the full AST and additionally swaps `import` callees in `CallExpression` nodes for `__omp_import__`, which forwards the optional options bag verbatim to native `import()` so `{ with: { type: "json" } }` round-trips. Renamed `rewriteStaticImports` → `rewriteImports`.
|
|
43
|
+
- Fixed `pr://<owner>/<repo>/diff` URLs for repositories named `diff` to continue resolving to PR list lookups instead of being parsed as short-form diff links
|
|
44
|
+
- Fixed `issue://<N>/diff` short form previously misparsing as a repo named `<N>/diff` and surfacing a confusing GraphQL "Could not resolve to a Repository" error. The numeric-host disambiguation that already routed `pr://<N>/diff` through the diff path now applies to both schemes, so `issue://<N>/diff[/…]` falls through to the existing "Issue views do not have a diff; use pr://<owner>/<repo>/<n>/diff for pull requests." rejection — matching the long-form `issue://<owner>/<repo>/<N>/diff` behavior. `<scheme>://<owner>/diff` listings for a repo literally named `diff` are unchanged.
|
|
45
|
+
- Fixed PR unified diff parsing so changed-file headers with quoted paths (such as paths containing spaces) are now detected correctly and hunk content lines beginning with `---`/`+++` are counted in additions/deletions
|
|
46
|
+
- Fixed GitHub view caching to account for active credential identity and avoid serving cached issue/PR data across different account/token contexts
|
|
47
|
+
- Fixed `read` call tracking so calls without an explicit path or URL target no longer appear as regular file reads in the execution tracker
|
|
48
|
+
- Fixed `createAgentSession()` subscribing the `credential_disabled` bridge to a freshly discovered `AuthStorage` orphan when an embedder supplied only `options.modelRegistry` (no `options.authStorage`). Refresh failures emitted by `modelRegistry.getApiKey()` flow through `modelRegistry.authStorage`, so a divergent local instance silently swallowed every disable event and also leaked into the `mcpManager` and session result. The SDK now reconciles `authStorage` to `modelRegistry.authStorage` up front and rejects mismatched `options.authStorage`/`options.modelRegistry.authStorage` pairs at session construction.
|
|
49
|
+
- Fixed `runSubagent` (subagent task executor) carrying the same latent `AuthStorage`/`ModelRegistry` divergence as `createAgentSession()`: when only `options.modelRegistry` was supplied, the executor previously fell through to a fresh `discoverAuthStorage()` and handed that orphan into `createAgentSession()` alongside a registry whose `.authStorage` was a different instance. The executor now reconciles to `modelRegistry.authStorage` before any further work and rejects mismatched `options.authStorage`/`options.modelRegistry.authStorage` pairs the same way the SDK does, so subagents can no longer silently observe a different storage view than their parent.
|
|
50
|
+
- Fixed `github` tool's `search_issues`/`search_prs`/`search_code`/`search_commits`/`search_repos` ops always returning 0 results when the query contained more than one qualifier (e.g. `is:merged is:pr`, `is:open author:foo`). `gh search …` since the `advanced_search=true` rollout in gh 2.92 silently wraps multi-token positional queries in parentheses and quotes everything after the first qualifier as that qualifier's value (`is:"merged is:pr"`), which GitHub then matches as a literal state filter that no PR can satisfy. The tool now calls `gh api -X GET /search/<endpoint> -f q=… -F per_page=…` directly so the qualifiers reach GitHub's search API verbatim. `is:issue`/`is:pr` and `repo:<owner>/<repo>` are appended internally to preserve the previous CLI-flag behavior; the user-facing query string in the formatted output is unchanged. `state` for merged PRs is derived from `pull_request.merged_at` so the rendered `State:` line stays `merged`/`closed`/`open` as before.
|
|
51
|
+
- Fixed `read` tool renderer rendering failed reads with a success check (`✓`) and styling the error message as file content while the surrounding box was red. The renderer now branches on `isError` for both file and URL paths: header shows `✘ Read <path>` with a proper error icon and the underlying message is rendered as an error line. `renderReadUrlResult` got the same treatment so failed URL reads also get the cross icon instead of falling through to the `"No response data"` Text fallback. Mirrors the `bash`/`find` renderer error pattern.
|
|
52
|
+
- Fixed ACP mode to advertise and handle non-TUI builtin slash commands and `/skill:<name>` commands
|
|
53
|
+
- Fixed ACP `session/resume` and `session/close` to dispatch correctly under SDK 0.21 by renaming `unstable_resumeSession` / `unstable_closeSession` to the stable `resumeSession` / `closeSession` method names the SDK now routes to
|
|
54
|
+
- Fixed ACP `tool_call` / `tool_call_update` `locations` to always emit absolute paths (resolved against the session cwd) so editor clients can reliably open or focus the referenced file
|
|
55
|
+
- Fixed ACP edit `diff` metadata for moves to point at the destination path rather than the now-deleted source so post-edit "open file" actions land on the new file
|
|
56
|
+
- Fixed ACP `session/request_permission` `locations` to be absolute and to honor the `requestPermission` capability bit instead of only checking for the method, matching the read/write/bash capability gating
|
|
57
|
+
- Fixed ACP `authenticate` to reject `methodId` values that were not advertised by `initialize` so malformed clients fail fast instead of being treated as authenticated
|
|
58
|
+
- Fixed ACP mode changes made via `session/set_session_config_option` (`MODE_CONFIG_ID`) to also emit a `current_mode_update` notification, matching `session/set_mode` so clients tracking `modes.currentModeId` stay in sync
|
|
59
|
+
- Fixed `/model` ACP builtin to emit a `config_option_update` after switching models so clients show the new model in config selectors immediately
|
|
60
|
+
- Fixed `/mcp list` (ACP) to redact query strings and userinfo from server URLs before emitting them, so API keys embedded in URLs (e.g. `?exaApiKey=…`) are not leaked to clients
|
|
61
|
+
- Fixed `/mcp test|resources|prompts` (ACP) to wire the auth storage before `prepareConfig` so OAuth-backed MCP servers can refresh tokens and inject `Authorization` headers
|
|
62
|
+
- Fixed `/mcp list`, `/mcp test`, `/mcp resources`, `/mcp prompts`, `/mcp enable`, and `/mcp disable` (ACP) to preserve project-over-user precedence when the same server name is defined in both scopes, matching the runtime capability merge so toggling the duplicated name flips the effective entry
|
|
63
|
+
- Fixed `/ssh add --port` parsing to reject non-integer values (e.g. `22oops`) instead of silently coercing them via `Number.parseInt`
|
|
64
|
+
- Fixed `/ssh list` to deduplicate hosts shared between project and user scopes, listing project entries first to match capability-loader precedence
|
|
65
|
+
- Fixed `/export` (ACP) to reject clipboard aliases (`--copy`, `clipboard`, `copy`) instead of using them as the output filename
|
|
66
|
+
- Fixed ACP builtin commands (`/compact`, `/force`, `/move`, `/browser`) to surface underlying failures via `output()` instead of swallowing them
|
|
67
|
+
- Fixed `/session save|delete` (ACP) to route through the active `SessionManager` so the persist writer is consulted and stale storage references are removed
|
|
68
|
+
- Fixed `/reload-plugins`, `/marketplace install|uninstall|upgrade`, and `/plugins enable|disable` (ACP) to refresh slash command registries and emit `available_commands_update` after plugin state changes
|
|
69
|
+
- Fixed ACP `usage()` text emission to be awaited so help and error output is not dropped or reordered when commands return immediately
|
|
70
|
+
- Fixed ACP `bash` tool to release the client terminal handle on `terminal/output` or `waitForExit` failures, and to race output polling against abort so a stuck RPC cannot delay cancellation
|
|
71
|
+
- Fixed ACP `resource` content blocks with `image/*` MIME types to be routed into the LLM `images` array instead of being dropped as opaque blobs
|
|
72
|
+
- Fixed `pr://` and `issue://` URLs accepting empty, `.`, or `..` path segments. `pr://owner//77`, `pr://owner/repo/77/diff//2`, and `pr://owner/../77/diff` previously slipped past the `.filter(Boolean)` split and were forwarded to `gh`; now they throw `Invalid <scheme>:// URL: empty or unsafe path segment` before any subprocess work.
|
|
73
|
+
- Fixed `read` of `issue://` / `pr://` URLs ignoring the read tool's `AbortSignal`. Aborting a long `pr://<N>/diff/all` or stale issue fetch now propagates into the resolver and short-circuits at the handler entry; previously the `gh` round-trip and cache write ran to completion.
|
|
74
|
+
- Fixed `read <path>:raw` (and the `raw: true` arg) still rendering markdown internal-URL content through the formatted markdown renderer. The TUI now respects the raw selector and falls back to the code-cell renderer so verbatim bytes are shown when requested.
|
|
75
|
+
- Fixed `eval` tool JS cells crashing with a `structuredClone` error when an awaited final expression returned a non-cloneable value (module namespace, function, symbol, etc.). `displayValue` now falls back to a text representation and logs at debug instead of throwing.
|
|
76
|
+
- Fixed `eval` tool JS rewriter missing the final expression when followed by trailing empty statements (e.g. `await Promise.resolve(1);;`). `returnFinalExpression` now scans backward past `EmptyStatement` nodes before deciding there is no final expression.
|
|
77
|
+
- Fixed `github` view cache evicting valid rows on open whenever a longer-than-default `github.cache.hardTtlSec` was configured. `openDb()` no longer sweeps with the 7-day default before settings load; the per-lookup `sweepIfDue()` enforces the configured retention exclusively.
|
|
78
|
+
- Fixed extension `ctx.shutdown()` being a no-op in the primary interactive path. The handler in `initHooksAndCustomTools` now sets `shutdownRequested` (mirroring the backgrounded-reinit path), and the main REPL loop drains the flag at the post-stream idle boundary so queued steering messages still flush before teardown.
|
|
79
|
+
- Fixed plan-mode "Approve and compact context" dispatching queued user input against the stale plan-mode reference path. `setPlanReferencePath(finalPlanFilePath)` now runs before `handleCompactCommand` flushes the compaction queue, so any message typed during compaction is delivered with the approved plan context attached.
|
|
80
|
+
- Fixed `.omp/commands/fix-issues.md` and `.omp/commands/review-prs.md` still instructing agents to call the removed `github issue_view` / `pr_view` / `pr_diff` ops; they now reference `read issue://<N>` and `read pr://<N>[/diff[/all|<i>]]`.
|
|
81
|
+
- Fixed `ExtensionRunner.initialize()` flushing buffered `credential_disabled` events before mode controllers had a chance to register their `onError` listener. Mode controllers call `runner.initialize(...)` immediately followed by `runner.onError(...)` synchronously; the flush now runs in a microtask after splicing the buffer, so a synchronously throwing `credential_disabled` handler is routed through the registered error listener instead of being silently dropped.
|
|
82
|
+
|
|
83
|
+
### Security
|
|
84
|
+
|
|
85
|
+
- Secured the GitHub cache store with strict file permissions (`0600` files) and private permissions for newly created cache directories (`0700`) to reduce local cache exposure
|
|
86
|
+
|
|
5
87
|
## [14.9.9] - 2026-05-12
|
|
6
88
|
|
|
7
89
|
### Added
|
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": "
|
|
4
|
+
"version": "15.0.0",
|
|
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",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "
|
|
52
|
-
"@oh-my-pi/pi-ai": "
|
|
53
|
-
"@oh-my-pi/pi-natives": "
|
|
54
|
-
"@oh-my-pi/pi-tui": "
|
|
55
|
-
"@oh-my-pi/pi-utils": "
|
|
50
|
+
"@oh-my-pi/omp-stats": "15.0.0",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "15.0.0",
|
|
52
|
+
"@oh-my-pi/pi-ai": "15.0.0",
|
|
53
|
+
"@oh-my-pi/pi-natives": "15.0.0",
|
|
54
|
+
"@oh-my-pi/pi-tui": "15.0.0",
|
|
55
|
+
"@oh-my-pi/pi-utils": "15.0.0",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34.49",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
|
@@ -25,7 +25,7 @@ const PROMPT_DIRS = [PROMPTS_DIR, COMMIT_PROMPTS_DIR, AGENTIC_PROMPTS_DIR];
|
|
|
25
25
|
const PROMPT_FORMAT_OPTIONS = {
|
|
26
26
|
renderPhase: "pre-render",
|
|
27
27
|
replaceAsciiSymbols: true,
|
|
28
|
-
|
|
28
|
+
normalizeRfc2119: true,
|
|
29
29
|
} as const;
|
|
30
30
|
|
|
31
31
|
async function main() {
|
package/src/cli/args.ts
CHANGED
|
@@ -7,7 +7,7 @@ import chalk from "chalk";
|
|
|
7
7
|
import { parseEffort } from "../thinking";
|
|
8
8
|
import { BUILTIN_TOOLS } from "../tools";
|
|
9
9
|
|
|
10
|
-
export type Mode = "text" | "json" | "rpc" | "acp";
|
|
10
|
+
export type Mode = "text" | "json" | "rpc" | "acp" | "rpc-ui";
|
|
11
11
|
|
|
12
12
|
export interface Args {
|
|
13
13
|
cwd?: string;
|
|
@@ -79,7 +79,7 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
79
79
|
result.allowHome = true;
|
|
80
80
|
} else if (arg === "--mode" && i + 1 < args.length) {
|
|
81
81
|
const mode = args[++i];
|
|
82
|
-
if (mode === "text" || mode === "json" || mode === "rpc" || mode === "acp") {
|
|
82
|
+
if (mode === "text" || mode === "json" || mode === "rpc" || mode === "acp" || mode === "rpc-ui") {
|
|
83
83
|
result.mode = mode;
|
|
84
84
|
}
|
|
85
85
|
} else if (arg === "--continue" || arg === "-c") {
|
package/src/cli.ts
CHANGED
|
@@ -50,6 +50,7 @@ process.title = APP_NAME;
|
|
|
50
50
|
|
|
51
51
|
const commands: CommandEntry[] = [
|
|
52
52
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
53
|
+
{ name: "acp", load: () => import("./commands/acp").then(m => m.default) },
|
|
53
54
|
{ name: "agents", load: () => import("./commands/agents").then(m => m.default) },
|
|
54
55
|
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
|
|
55
56
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Oh My Pi as an ACP (Agent Client Protocol) server over stdio.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around the launch flow that forces `mode: "acp"` unless the
|
|
5
|
+
* ACP terminal-auth flag asks the same command to open the interactive TUI.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "@oh-my-pi/pi-utils/cli";
|
|
8
|
+
import { parseArgs } from "../cli/args";
|
|
9
|
+
import { runRootCommand } from "../main";
|
|
10
|
+
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
11
|
+
|
|
12
|
+
export default class Acp extends Command {
|
|
13
|
+
static description = "Run Oh My Pi as an ACP (Agent Client Protocol) server over stdio";
|
|
14
|
+
static strict = false;
|
|
15
|
+
|
|
16
|
+
async run(): Promise<void> {
|
|
17
|
+
const { args, terminalAuth } = prepareAcpTerminalAuthArgs(this.argv);
|
|
18
|
+
const parsed = parseArgs(args);
|
|
19
|
+
if (!terminalAuth) {
|
|
20
|
+
parsed.mode = "acp";
|
|
21
|
+
}
|
|
22
|
+
await runRootCommand(parsed, args);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
8
8
|
import { parseArgs } from "../cli/args";
|
|
9
9
|
import { runRootCommand } from "../main";
|
|
10
|
+
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
10
11
|
|
|
11
12
|
export default class Index extends Command {
|
|
12
13
|
static description = "AI coding assistant";
|
|
@@ -49,8 +50,8 @@ export default class Index extends Command {
|
|
|
49
50
|
description: "Allow starting in ~ without auto-switching to a temp dir",
|
|
50
51
|
}),
|
|
51
52
|
mode: Flags.string({
|
|
52
|
-
description: "Output mode: text (default), json, or rpc",
|
|
53
|
-
options: ["text", "json", "rpc"],
|
|
53
|
+
description: "Output mode: text (default), json, rpc, or rpc-ui",
|
|
54
|
+
options: ["text", "json", "rpc", "acp", "rpc-ui"],
|
|
54
55
|
}),
|
|
55
56
|
print: Flags.boolean({
|
|
56
57
|
char: "p",
|
|
@@ -135,7 +136,8 @@ export default class Index extends Command {
|
|
|
135
136
|
static strict = false;
|
|
136
137
|
|
|
137
138
|
async run(): Promise<void> {
|
|
138
|
-
const
|
|
139
|
-
|
|
139
|
+
const { args } = prepareAcpTerminalAuthArgs(this.argv);
|
|
140
|
+
const parsed = parseArgs(args);
|
|
141
|
+
await runRootCommand(parsed, args);
|
|
140
142
|
}
|
|
141
143
|
}
|
|
@@ -34,5 +34,5 @@ Tool guidance:
|
|
|
34
34
|
|
|
35
35
|
## Changelog Requirements
|
|
36
36
|
|
|
37
|
-
If changelog targets provided, you
|
|
37
|
+
If changelog targets provided, you MUST call `propose_changelog` before finishing.
|
|
38
38
|
If you propose split commit plan, include changelog target files in relevant commit changes.
|
|
@@ -956,6 +956,36 @@ export async function resolveModelScope(
|
|
|
956
956
|
return scopedModels;
|
|
957
957
|
}
|
|
958
958
|
|
|
959
|
+
/**
|
|
960
|
+
* Resolve the set of models a session is allowed to use, given the active
|
|
961
|
+
* settings. Starts from `modelRegistry.getAvailable()` (so disabled providers
|
|
962
|
+
* and providers without credentials are already filtered out) and, when
|
|
963
|
+
* `enabledModels` is configured for the current path scope, further restricts
|
|
964
|
+
* the result to models matching those patterns.
|
|
965
|
+
*
|
|
966
|
+
* Returns the unfiltered available list when `enabledModels` is empty.
|
|
967
|
+
* Returns an empty list when `enabledModels` is configured but no available
|
|
968
|
+
* model matches any pattern — callers MUST treat this as "no usable model"
|
|
969
|
+
* rather than falling back to the global default (see issue #1022).
|
|
970
|
+
*/
|
|
971
|
+
export async function resolveAllowedModels(
|
|
972
|
+
modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">,
|
|
973
|
+
settings: Settings | undefined,
|
|
974
|
+
preferences?: ModelMatchPreferences,
|
|
975
|
+
): Promise<Model<Api>[]> {
|
|
976
|
+
const available = modelRegistry.getAvailable();
|
|
977
|
+
const patterns = settings?.get("enabledModels");
|
|
978
|
+
if (!patterns || patterns.length === 0) {
|
|
979
|
+
return available;
|
|
980
|
+
}
|
|
981
|
+
const scoped = await resolveModelScope(patterns, modelRegistry, preferences);
|
|
982
|
+
if (scoped.length === 0) {
|
|
983
|
+
return [];
|
|
984
|
+
}
|
|
985
|
+
const allowed = new Set(scoped.map(entry => `${entry.model.provider}/${entry.model.id}`));
|
|
986
|
+
return available.filter(model => allowed.has(`${model.provider}/${model.id}`));
|
|
987
|
+
}
|
|
988
|
+
|
|
959
989
|
export interface ResolveCliModelResult {
|
|
960
990
|
model: Model<Api> | undefined;
|
|
961
991
|
selector?: string;
|
|
@@ -1864,6 +1864,37 @@ export const SETTINGS_SCHEMA = {
|
|
|
1864
1864
|
},
|
|
1865
1865
|
},
|
|
1866
1866
|
|
|
1867
|
+
"github.cache.enabled": {
|
|
1868
|
+
type: "boolean",
|
|
1869
|
+
default: true,
|
|
1870
|
+
ui: {
|
|
1871
|
+
tab: "tools",
|
|
1872
|
+
label: "GitHub view cache",
|
|
1873
|
+
description: "Cache rendered issue/PR view output in ~/.omp/cache/github-cache.db so repeated reads are free",
|
|
1874
|
+
},
|
|
1875
|
+
},
|
|
1876
|
+
|
|
1877
|
+
"github.cache.softTtlSec": {
|
|
1878
|
+
type: "number",
|
|
1879
|
+
default: 300,
|
|
1880
|
+
ui: {
|
|
1881
|
+
tab: "tools",
|
|
1882
|
+
label: "GitHub cache soft TTL (seconds)",
|
|
1883
|
+
description: "Within this window, cached issue/PR view rows are returned directly. Default 5 minutes.",
|
|
1884
|
+
},
|
|
1885
|
+
},
|
|
1886
|
+
|
|
1887
|
+
"github.cache.hardTtlSec": {
|
|
1888
|
+
type: "number",
|
|
1889
|
+
default: 604800,
|
|
1890
|
+
ui: {
|
|
1891
|
+
tab: "tools",
|
|
1892
|
+
label: "GitHub cache hard TTL (seconds)",
|
|
1893
|
+
description:
|
|
1894
|
+
"Past soft TTL but within hard TTL, the tool returns the cached row and refreshes it in the background. Past hard TTL, the row is dropped. Default 7 days.",
|
|
1895
|
+
},
|
|
1896
|
+
},
|
|
1897
|
+
|
|
1867
1898
|
"web_search.enabled": {
|
|
1868
1899
|
type: "boolean",
|
|
1869
1900
|
default: true,
|
package/src/edit/index.ts
CHANGED
|
@@ -145,13 +145,15 @@ async function executeApplyPatchPerFile(
|
|
|
145
145
|
const result = await run(batchRequest);
|
|
146
146
|
const details = result.details;
|
|
147
147
|
perFileResults.push({
|
|
148
|
-
path,
|
|
148
|
+
path: details?.path ?? path,
|
|
149
149
|
diff: details?.diff ?? "",
|
|
150
150
|
firstChangedLine: details?.firstChangedLine,
|
|
151
151
|
diagnostics: details?.diagnostics,
|
|
152
152
|
op: details?.op,
|
|
153
153
|
move: details?.move,
|
|
154
154
|
meta: details?.meta,
|
|
155
|
+
oldText: details?.oldText,
|
|
156
|
+
newText: details?.newText,
|
|
155
157
|
});
|
|
156
158
|
const text = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
157
159
|
if (text) contentTexts.push(text);
|
|
@@ -205,6 +207,11 @@ async function executeSinglePathEntries(
|
|
|
205
207
|
const diffTexts: string[] = [];
|
|
206
208
|
let firstChangedLine: number | undefined;
|
|
207
209
|
let errorCount = 0;
|
|
210
|
+
let metadataPath: string | undefined;
|
|
211
|
+
let hasFirstOldText = false;
|
|
212
|
+
let firstOldText: string | undefined;
|
|
213
|
+
let hasLastNewText = false;
|
|
214
|
+
let lastNewText: string | undefined;
|
|
208
215
|
|
|
209
216
|
for (let i = 0; i < runs.length; i++) {
|
|
210
217
|
const isLast = i === runs.length - 1;
|
|
@@ -217,6 +224,17 @@ async function executeSinglePathEntries(
|
|
|
217
224
|
const details = result.details;
|
|
218
225
|
if (details?.diff) diffTexts.push(details.diff);
|
|
219
226
|
firstChangedLine ??= details?.firstChangedLine;
|
|
227
|
+
if (details?.path) {
|
|
228
|
+
metadataPath ??= details.path;
|
|
229
|
+
}
|
|
230
|
+
if (details && "oldText" in details && !hasFirstOldText) {
|
|
231
|
+
firstOldText = details.oldText;
|
|
232
|
+
hasFirstOldText = true;
|
|
233
|
+
}
|
|
234
|
+
if (details && "newText" in details) {
|
|
235
|
+
lastNewText = details.newText;
|
|
236
|
+
hasLastNewText = true;
|
|
237
|
+
}
|
|
220
238
|
const text = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
221
239
|
if (text) contentTexts.push(text);
|
|
222
240
|
} catch (err) {
|
|
@@ -242,6 +260,9 @@ async function executeSinglePathEntries(
|
|
|
242
260
|
details: {
|
|
243
261
|
diff: diffTexts.join("\n"),
|
|
244
262
|
firstChangedLine,
|
|
263
|
+
path: metadataPath ?? path,
|
|
264
|
+
...(hasFirstOldText ? { oldText: firstOldText } : {}),
|
|
265
|
+
...(hasLastNewText ? { newText: lastNewText } : {}),
|
|
245
266
|
},
|
|
246
267
|
// Any per-entry failure marks the aggregate result as an error so the
|
|
247
268
|
// renderer takes the error branch instead of falling through to the
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -1772,15 +1772,25 @@ export async function executePatchSingle(
|
|
|
1772
1772
|
.diagnostics(mergedDiagnostics?.summary ?? "", mergedDiagnostics?.messages ?? [])
|
|
1773
1773
|
.get();
|
|
1774
1774
|
|
|
1775
|
+
const oldText = result.change.type !== "create" ? result.change.oldContent : undefined;
|
|
1776
|
+
const newText = result.change.type !== "delete" ? result.change.newContent : undefined;
|
|
1777
|
+
|
|
1775
1778
|
return {
|
|
1776
1779
|
content: [{ type: "text", text: resultText }],
|
|
1777
1780
|
details: {
|
|
1778
1781
|
diff: diffResult.diff,
|
|
1782
|
+
// When the patch moves the file, anchor the diff to the destination
|
|
1783
|
+
// path. ACP `ToolCallContent.diff.path` comes from this field, and
|
|
1784
|
+
// clients use it to open or focus the file post-change; pointing at
|
|
1785
|
+
// the (now-deleted) source navigates to nothing.
|
|
1786
|
+
path: result.change.newPath ?? resolvedPath,
|
|
1779
1787
|
firstChangedLine: diffResult.firstChangedLine,
|
|
1780
1788
|
diagnostics: mergedDiagnostics,
|
|
1781
1789
|
op,
|
|
1782
1790
|
move: effectiveRename,
|
|
1783
1791
|
meta,
|
|
1792
|
+
oldText,
|
|
1793
|
+
newText,
|
|
1784
1794
|
},
|
|
1785
1795
|
};
|
|
1786
1796
|
}
|
|
@@ -1094,9 +1094,12 @@ export async function executeReplaceSingle(
|
|
|
1094
1094
|
content: [{ type: "text", text: resultText }],
|
|
1095
1095
|
details: {
|
|
1096
1096
|
diff: diffResult.diff,
|
|
1097
|
+
path: absolutePath,
|
|
1097
1098
|
firstChangedLine: diffResult.firstChangedLine,
|
|
1098
1099
|
diagnostics,
|
|
1099
1100
|
meta,
|
|
1101
|
+
oldText: rawContent,
|
|
1102
|
+
newText: finalContent,
|
|
1100
1103
|
},
|
|
1101
1104
|
};
|
|
1102
1105
|
}
|
package/src/edit/renderer.ts
CHANGED
|
@@ -55,6 +55,10 @@ export interface EditToolPerFileResult {
|
|
|
55
55
|
* Set when the underlying error carries a `displayMessage` (e.g. {@link HashlineMismatchError}). */
|
|
56
56
|
displayErrorText?: string;
|
|
57
57
|
meta?: OutputMeta;
|
|
58
|
+
/** Source-of-truth content before the edit; `undefined` for create operations. */
|
|
59
|
+
oldText?: string;
|
|
60
|
+
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
61
|
+
newText?: string;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
export interface EditToolDetails {
|
|
@@ -72,6 +76,12 @@ export interface EditToolDetails {
|
|
|
72
76
|
meta?: OutputMeta;
|
|
73
77
|
/** Per-file results (multi-file edits) */
|
|
74
78
|
perFileResults?: EditToolPerFileResult[];
|
|
79
|
+
/** Absolute file path for single-file edit results. Required by ACP diff metadata consumers. */
|
|
80
|
+
path?: string;
|
|
81
|
+
/** Source-of-truth content before the edit; `undefined` for create operations. */
|
|
82
|
+
oldText?: string;
|
|
83
|
+
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
84
|
+
newText?: string;
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
WorkerOutbound,
|
|
18
18
|
} from "./worker-protocol";
|
|
19
19
|
|
|
20
|
-
export {
|
|
20
|
+
export { rewriteImports, wrapCode } from "./shared/rewrite-imports";
|
|
21
21
|
export type { JsDisplayOutput } from "./worker-protocol";
|
|
22
22
|
|
|
23
23
|
export interface VmRunState {
|