@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/examples/custom-tools/README.md +11 -7
  3. package/examples/custom-tools/hello/index.ts +2 -2
  4. package/examples/extensions/README.md +19 -8
  5. package/examples/extensions/api-demo.ts +15 -19
  6. package/examples/extensions/hello.ts +5 -6
  7. package/examples/extensions/plan-mode.ts +1 -1
  8. package/examples/extensions/reload-runtime.ts +4 -3
  9. package/examples/extensions/with-deps/index.ts +4 -3
  10. package/examples/sdk/06-extensions.ts +4 -2
  11. package/package.json +8 -18
  12. package/src/autoresearch/tools/init-experiment.ts +38 -41
  13. package/src/autoresearch/tools/log-experiment.ts +32 -41
  14. package/src/autoresearch/tools/run-experiment.ts +3 -3
  15. package/src/autoresearch/tools/update-notes.ts +11 -11
  16. package/src/commands/commit.ts +10 -0
  17. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  18. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  19. package/src/commit/agentic/tools/git-hunk.ts +5 -5
  20. package/src/commit/agentic/tools/git-overview.ts +4 -4
  21. package/src/commit/agentic/tools/propose-changelog.ts +13 -13
  22. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  23. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  24. package/src/commit/agentic/tools/schemas.ts +28 -28
  25. package/src/commit/agentic/tools/split-commit.ts +22 -21
  26. package/src/commit/analysis/summary.ts +4 -4
  27. package/src/commit/changelog/generate.ts +7 -11
  28. package/src/commit/shared-llm.ts +22 -34
  29. package/src/config/config-file.ts +35 -13
  30. package/src/config/model-registry.ts +40 -191
  31. package/src/config/models-config-schema.ts +166 -0
  32. package/src/config/settings-schema.ts +29 -0
  33. package/src/discovery/claude-plugins.ts +19 -7
  34. package/src/edit/index.ts +2 -2
  35. package/src/edit/modes/apply-patch.ts +7 -6
  36. package/src/edit/modes/patch.ts +18 -25
  37. package/src/edit/modes/replace.ts +18 -20
  38. package/src/eval/js/shared/rewrite-imports.ts +131 -10
  39. package/src/eval/py/executor.ts +233 -623
  40. package/src/eval/py/kernel.ts +27 -2
  41. package/src/eval/py/runner.py +42 -11
  42. package/src/eval/py/runtime.ts +1 -0
  43. package/src/exa/factory.ts +5 -4
  44. package/src/exa/mcp-client.ts +1 -1
  45. package/src/exa/researcher.ts +9 -20
  46. package/src/exa/search.ts +26 -52
  47. package/src/exa/types.ts +1 -1
  48. package/src/exa/websets.ts +54 -53
  49. package/src/exec/bash-executor.ts +2 -1
  50. package/src/extensibility/custom-commands/loader.ts +5 -3
  51. package/src/extensibility/custom-commands/types.ts +4 -2
  52. package/src/extensibility/custom-tools/loader.ts +5 -3
  53. package/src/extensibility/custom-tools/types.ts +7 -6
  54. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  55. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  56. package/src/extensibility/extensions/loader.ts +7 -3
  57. package/src/extensibility/extensions/types.ts +9 -5
  58. package/src/extensibility/extensions/wrapper.ts +1 -2
  59. package/src/extensibility/hooks/loader.ts +3 -1
  60. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  61. package/src/extensibility/hooks/types.ts +4 -2
  62. package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
  63. package/src/extensibility/shared-events.ts +1 -1
  64. package/src/extensibility/typebox.ts +391 -0
  65. package/src/goals/tools/goal-tool.ts +6 -12
  66. package/src/hashline/input.ts +2 -1
  67. package/src/hashline/parser.ts +27 -3
  68. package/src/hashline/types.ts +4 -4
  69. package/src/hindsight/state.ts +2 -2
  70. package/src/index.ts +0 -2
  71. package/src/internal-urls/docs-index.generated.ts +15 -15
  72. package/src/internal-urls/router.ts +8 -0
  73. package/src/internal-urls/types.ts +21 -0
  74. package/src/lsp/config.ts +15 -6
  75. package/src/lsp/defaults.json +6 -2
  76. package/src/lsp/types.ts +30 -38
  77. package/src/mcp/manager.ts +1 -1
  78. package/src/mcp/tool-bridge.ts +1 -1
  79. package/src/modes/acp/acp-agent.ts +248 -50
  80. package/src/modes/components/session-observer-overlay.ts +12 -1
  81. package/src/modes/components/status-line/segments.ts +39 -4
  82. package/src/modes/controllers/command-controller.ts +27 -2
  83. package/src/modes/controllers/event-controller.ts +3 -4
  84. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  85. package/src/modes/interactive-mode.ts +1 -1
  86. package/src/modes/rpc/host-tools.ts +1 -1
  87. package/src/modes/rpc/host-uris.ts +235 -0
  88. package/src/modes/rpc/rpc-client.ts +1 -1
  89. package/src/modes/rpc/rpc-mode.ts +27 -1
  90. package/src/modes/rpc/rpc-types.ts +58 -1
  91. package/src/modes/runtime-init.ts +2 -1
  92. package/src/modes/theme/defaults/dark-poimandres.json +1 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +1 -0
  94. package/src/modes/theme/theme.ts +117 -117
  95. package/src/modes/types.ts +1 -1
  96. package/src/modes/utils/context-usage.ts +2 -2
  97. package/src/prompts/tools/github.md +4 -4
  98. package/src/prompts/tools/hashline.md +22 -26
  99. package/src/prompts/tools/read.md +55 -37
  100. package/src/sdk.ts +31 -8
  101. package/src/session/agent-session.ts +74 -104
  102. package/src/session/messages.ts +16 -51
  103. package/src/session/session-manager.ts +22 -2
  104. package/src/session/streaming-output.ts +16 -6
  105. package/src/task/discovery.ts +5 -2
  106. package/src/task/executor.ts +210 -87
  107. package/src/task/index.ts +15 -11
  108. package/src/task/render.ts +32 -5
  109. package/src/task/types.ts +54 -39
  110. package/src/tools/ask.ts +12 -12
  111. package/src/tools/ast-edit.ts +11 -15
  112. package/src/tools/ast-grep.ts +9 -10
  113. package/src/tools/bash-command-fixup.ts +47 -0
  114. package/src/tools/bash.ts +48 -38
  115. package/src/tools/browser/render.ts +2 -2
  116. package/src/tools/browser.ts +39 -53
  117. package/src/tools/calculator.ts +12 -11
  118. package/src/tools/checkpoint.ts +7 -7
  119. package/src/tools/debug.ts +40 -43
  120. package/src/tools/eval.ts +16 -10
  121. package/src/tools/find.ts +10 -13
  122. package/src/tools/gh.ts +108 -132
  123. package/src/tools/hindsight-recall.ts +4 -6
  124. package/src/tools/hindsight-reflect.ts +5 -5
  125. package/src/tools/hindsight-retain.ts +15 -17
  126. package/src/tools/image-gen.ts +31 -81
  127. package/src/tools/index.ts +4 -1
  128. package/src/tools/inspect-image.ts +8 -9
  129. package/src/tools/irc.ts +15 -27
  130. package/src/tools/job.ts +30 -28
  131. package/src/tools/output-meta.ts +26 -0
  132. package/src/tools/read.ts +39 -12
  133. package/src/tools/recipe/index.ts +7 -9
  134. package/src/tools/render-mermaid.ts +12 -12
  135. package/src/tools/report-tool-issue.ts +4 -4
  136. package/src/tools/resolve.ts +11 -11
  137. package/src/tools/review.ts +14 -26
  138. package/src/tools/search-tool-bm25.ts +7 -9
  139. package/src/tools/search.ts +19 -22
  140. package/src/tools/ssh.ts +10 -9
  141. package/src/tools/todo-write.ts +26 -34
  142. package/src/tools/vim.ts +10 -26
  143. package/src/tools/write.ts +25 -5
  144. package/src/tools/yield.ts +100 -54
  145. package/src/web/search/index.ts +9 -24
  146. package/src/web/search/providers/anthropic.ts +5 -0
  147. package/src/web/search/providers/exa.ts +3 -0
  148. package/src/web/search/providers/gemini.ts +5 -0
  149. package/src/web/search/providers/jina.ts +5 -2
  150. package/src/web/search/providers/zai.ts +5 -2
  151. package/src/prompts/compaction/branch-summary-context.md +0 -5
  152. package/src/prompts/compaction/branch-summary-preamble.md +0 -2
  153. package/src/prompts/compaction/branch-summary.md +0 -30
  154. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  155. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  156. package/src/prompts/compaction/compaction-summary.md +0 -38
  157. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  158. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  159. package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
  160. package/src/prompts/system/file-operations.md +0 -10
  161. package/src/prompts/system/handoff-document.md +0 -49
  162. package/src/prompts/system/summarization-system.md +0 -3
  163. package/src/session/compaction/branch-summarization.ts +0 -324
  164. package/src/session/compaction/compaction.ts +0 -1420
  165. package/src/session/compaction/errors.ts +0 -31
  166. package/src/session/compaction/index.ts +0 -8
  167. package/src/session/compaction/pruning.ts +0 -91
  168. package/src/session/compaction/utils.ts +0 -184
package/CHANGELOG.md CHANGED
@@ -2,6 +2,98 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.1.0] - 2026-05-15
6
+ ### Breaking Changes
7
+
8
+ - Changed the extension and hook runtime API by moving schema typing from direct TypeBox imports to `TSchema` from `@oh-my-pi/pi-ai`, requiring callers who use TypeScript imports of `Type` to migrate via provided injected modules
9
+
10
+ ### Added
11
+
12
+ - Added a cancellable handoff progress indicator in `/handoff` that displays while handoff generation runs and can be aborted with `Esc`
13
+ - Added `apiKey` as a supported provider override field in model config, allowing API-key-only overrides to provide fallback credentials for built-in models
14
+ - Added `supportsMultipleSystemMessages`, `allowsSyntheticReasoningContentForToolCalls`, `disableReasoningOnToolChoice`, and `levels` model-thinking compatibility fields to model configuration schemas
15
+ - Added `zod` to the Extension, Custom Tool, Hook, and Custom Command APIs as `pi.zod` so extension and plugin authors can define tool schemas with Zod without separate imports
16
+ - Added `pi.zod` as a canonical schema API for examples and extension plugins while keeping `typebox` available as legacy compatibility
17
+ - Added a `telemetry` option to `createAgentSession` for passing OpenTelemetry configuration through to the underlying Agent
18
+
19
+ ### Changed
20
+
21
+ - Changed handoff generation to run as a one-shot handoff request and switch to the new session only after it completes, avoiding an extra assistant handoff turn in chat history
22
+ - Changed `pi.typebox.Type.Composite` to merge all object schemas in the provided list, enabling more than two object inputs
23
+ - Changed `pi.typebox.Type.Record` to validate record keys against the provided key schema instead of forcing string keys
24
+ - Changed `pi.typebox.Type.Array` with `uniqueItems: true` to reject duplicate items while preserving the constraint in wire schemas
25
+ - Changed `pi.typebox.Type.Object` with `additionalProperties: false` to reject unknown properties during parsing
26
+ - Changed `pi.typebox.Type.Enum` in the compatibility shim to preserve numeric TypeScript enum values
27
+ - Changed tool parameter schemas across the agent to use the shared Pi schema pipeline (`TSchema` plus Zod/JSON Schema validation) instead of direct AJV/TypeBox compilation for stricter schema validation compatibility
28
+ - Changed GitHub tool input schema shape to expose operation fields in a flat schema form without legacy `run_watch`-style nesting
29
+ - Changed Python session pooling to remove the previous 4-session retention cap and 5-minute idle-session eviction, so kernels now stay alive for a session until explicitly disposed via `disposeKernelSessionsByOwner` or `disposeAllKernelSessions`
30
+ - Changed kernel cleanup behavior to avoid automatic eviction by idle timeout and capacity pressure, so additional Python sessions are not queued behind retained-session shutdown retries
31
+ - Replaced the bundled `@sinclair/typebox` runtime dependency with an in-repo Zod-backed shim exposed through `pi.typebox.Type.*`. Common builders (`Object`, `String`, `Number`, `Integer`, `Boolean`, `Array`, `Tuple`, `Union`, `Intersect`, `Literal`, `Enum`, `Optional`, `Nullable`, `Record`, `Partial`, `Required`, `Pick`, `Omit`, `Composite`, …) keep their existing call signatures but now return Zod schemas that flow through the same validation/wire pipeline as `pi.zod`. Bare `@sinclair/typebox` imports inside extensions are transparently remapped to the same shim by the runtime plugin shim, so plugins that authored against `import { Type } from "@sinclair/typebox"` keep working unchanged. Plugins that relied on TypeBox-only submodule APIs (`@sinclair/typebox/compiler`, `@sinclair/typebox/value`, `TypeRegistry`, the `Symbol(TypeBox.Kind)` marker) must vendor `@sinclair/typebox` in their own package — only the root import is remapped.
32
+
33
+ ### Deprecated
34
+
35
+ - Deprecated direct TypeBox-only examples for plugin schemas by updating example documentation to prefer `pi.zod`
36
+
37
+ ### Fixed
38
+
39
+ - Fixed auto-triggered handoff flow to perform only a single handoff-generation model call instead of an extra prompt cycle
40
+ - Fixed handoff cancellation behavior so a pre-cancelled signal returns `Handoff cancelled` without starting generation and aborting handoff now propagates through the handoff request signal
41
+ - Fixed `create_conventional_analysis` parsing to ignore harmless extra fields and still parse the required conventional fields
42
+ - Fixed BashTool async request validation flow so async execution remains disabled and returns the explicit `Async bash execution is disabled` error
43
+ - Fixed `task.simple` invalid `schema` and `context` argument handling to still reject unsupported fields after tool-argument validation
44
+ - Fixed subagent execution hangs by enforcing `task.maxRuntimeMs` as a wall-clock limit even when inference streaming stalls, so stuck subagents now abort and report runtime-limit exceeded
45
+ - Fixed tool schema compatibility validation by routing TypeBox schemas through shared conversion and Zod-based validation to avoid strict-schema provider mismatches
46
+ - Fixed Python execution cancellation and timeouts by escalating to kernel shutdown if `SIGINT` did not terminate a running cell within 2 seconds, preventing indefinite hangs in queued or stuck sessions
47
+ - Fixed cleanup blocking during long-running executions by forcing a kernel shutdown path when interrupt-based cancellation is ignored
48
+ - Fixed bash output emitting a spurious `[… 0 lines elided (NB) …]` marker (and reordering the artifact link before the command output) when the shell minimizer rewrote a small command's output. After `OutputSink.replace()` swapped the minimized text into the buffer, the subsequent `sink.push("[raw output: artifact://N]\n")` chunk was funneled back into the (now empty) head-retention window while the pre-replace `#totalBytes` still tracked the original raw stream — so `dump()` composed `<head=artifact-link> + <middle-elision marker against stale totals> + <tail=minimized text>` instead of `<minimized text> + <artifact link>`. `replace()` now realigns `#totalBytes`/`#totalLines`/`#sawData`/`#truncated` to the authoritative buffer and disables head retention for the lifetime of the sink, so further pushes append to the tail buffer in order. The bash executor also drops the leading `\n` on the artifact-link push when the minimized text already ends with one so the separator stays single-newline.
49
+ - Fixed legacy plugin extensions failing to load on Windows when they import a bare-specifier dependency from their own `node_modules` (e.g. `import YAML from "yaml"` in `supipowers`). The legacy-pi mirror resolved the dependency to its absolute path and then ran the path through `isUrlLikeSpecifier`, whose `^[A-Za-z][A-Za-z\d+.-]*:` regex matched the Windows drive letter (`C:`) and short-circuited the `pathToFileURL` conversion. The raw path was emitted into the mirrored TS source as `import x from "C:\\Users\\...\\dep\\dist\\index.js"`, where `\n`, `\U`, `\y` and other backslash sequences were eaten by the TS string-literal parser, producing nonsense package specifiers like `C:Usersjames.ompagentextensionssupipowers\node_modulesyamldistindex.js` that Bun's resolver rejected with `Cannot find package …`. `isUrlLikeSpecifier` now rejects `^[A-Za-z]:[\\/]` first, so Windows absolute paths flow through `pathToFileURL` like every other absolute path and reach the mirror as proper `file:///C:/...` URLs.
50
+ - Fixed Python session queued executions silently resurrecting kernels after `disposeAllKernelSessions` or `disposeKernelSessionsByOwner` removed the session: queued work now checks the session is still registered before replacing or executing on a kernel and rejects with cancellation otherwise
51
+ - Fixed Python session disposal treating an unconfirmed `PythonKernel.shutdown()` result as success: sessions whose kernel shutdown returns `{ confirmed: false }` (or rejects) are now retained in the registry and a `warn` is logged so a later dispose can retry instead of orphaning the subprocess
52
+ - Fixed `task.maxRuntimeMs` losing wall-clock aborts that fired during pre-prompt session setup by re-checking the abort signal immediately before issuing the model prompt, so a stalled subagent now exits with the runtime-limit reason instead of hanging through setup races
53
+ - Fixed late `yield` events landing after a wall-clock timeout from flipping a timed-out subagent to a successful exit, so the reported `aborted` flag and exit code now always reflect the runtime-limit breach while yield payloads remain in `extractedToolData`
54
+ - Fixed async-task progress consumer to copy `contextTokens` and `contextWindow` from the completed `SingleResult` onto `AgentProgress`, so UI gauges keep showing per-turn context after a backgrounded task finishes
55
+ - Fixed the status-line `path` segment ignoring `stripWorkPrefix: false` when selecting the folder icon for scratch directories. The icon selection now respects the same gate as the scratch path stripping, so disabling `stripWorkPrefix` keeps the regular `folder` icon even when the project directory is inside a scratch root.
56
+ - Fixed `YieldTool` constructor to fall back to the loose record schema when the session `outputSchema` contains unresolved `$ref` strings (e.g. external or cyclic references that survive dereferencing), instead of installing a validator that would reject every payload with an unresolved-reference error
57
+ - Fixed `pi.typebox.Type.String` dropping `minLength`/`maxLength`/`pattern` constraints when a `format` (e.g. `email`, `url`, `uuid`) was also supplied; length and pattern checks are now applied to the format-specific schema instead of being gated on an `instanceof z.ZodString` check that never matched the format subclasses.
58
+ - Fixed `pi.typebox.Type.Object` stripping unknown properties when constructed without explicit `additionalProperties`. TypeBox preserves extras by default, so the shim now installs `.loose()` for the omitted/`true` cases while keeping `.strict()` for `additionalProperties: false` and `.catchall(schema)` for a schema value.
59
+
60
+ ## [15.0.2] - 2026-05-15
61
+
62
+ ### Added
63
+
64
+ - Added the `set_host_uri_schemes` RPC command so hosts can register and replace writable/read-only internal URI schemes with scheme metadata (`writable`, `immutable`) at runtime
65
+ - Enabled the `write` tool to dispatch `write(url, content)` to registered internal URL handlers, allowing edits to non-filesystem resources via host-managed URI schemes
66
+ - Added host-owned internal URI read/write over RPC, including abort support, so URI operations are resolved by the host transport for `read` and `write` requests
67
+ - Added handling of host URI request results in RPC mode so host services can stream completion frames for internal URI operations
68
+ - Added scratch-directory awareness to the status-line `path` segment. When the project directory is inside an OS-level scratch root (the platform `os.tmpdir()`, `/tmp` and `/var/tmp` plus their macOS `/private/...` aliases, `~/tmp`, or — on Windows — `%TEMP%` / `%TMP%` / `%SystemRoot%\Temp`), the segment now (1) renders the new `icon.scratchFolder` symbol instead of `icon.folder`, and (2) strips the scratch root from the displayed path so only the trailing folder (and any subpath beneath it) is shown — mirroring how `/work` and `~/Projects` are already abbreviated. Both behaviors honor the existing `stripWorkPrefix` option. Icon defaults: 🗑 (emoji), `` (nf-fa-trash) for Nerd Font, `[T]` for ASCII, `◌` in the poimandres themes; themes can override `icon.scratchFolder` independently of `icon.folder`.
69
+
70
+ ### Changed
71
+
72
+ - Changed the `github` tool's search ops (`search_issues`, `search_prs`, `search_code`, `search_commits`) to default the `repo` scope to the current checkout's `owner/repo` when `repo` is omitted. The auto-scope is skipped when the query already carries an explicit `repo:`/`org:`/`user:`/`owner:` qualifier or when `gh repo view` cannot resolve a github remote (in which case the search proceeds across all of GitHub as before). `search_repos` is unchanged — repository-scoping there must live in the query.
73
+
74
+ - Changed bash command preprocessing to strip trailing `| head` and `| tail` pipelines (including `|&`) from each top-level segment in command chains separated by `;`, `&&`, `||`, or `&`
75
+ - Changed bash fixup notices to state that stderr is already merged into stdout and to reflect that fixes were applied for multiple stripped segments when several transforms fire
76
+ - Changed shell-minimizer per-line truncation marker from a bare `…` to `…[+N]`, where `N` is the count of dropped Unicode scalars. The bracketed tally disambiguates minimizer-driven cuts from genuine `…` characters in the source (paths, JSON, stack traces, etc.) and gives the agent an exact count so it can decide whether the missing tail is recoverable inline or warrants reading the `[raw output: artifact://<id>]` footer the bash wrapper already emits when the minimizer rewrites output. Affects pipeline Stage 5 (`truncate_lines_at` in `defs/*.toml`) and the internal callers in `filters/git.rs`, `filters/listing.rs`, and `filters/lint.rs`. ([#1046](https://github.com/can1357/oh-my-pi/issues/1046))
77
+ - Changed bash command preprocessing to use the real `brush-parser` AST via `pi-natives` `applyBashFixups` instead of a hand-rolled top-level mask scanner. The previous regex/character-walking implementation reimplemented quote/heredoc/`$(...)` tracking with conservative bail-outs (notably refusing to fixup commands containing here-strings); the AST-driven version inherits the full shell parser, so semantics-preserving rewrites like stripping `| head -5` off `cat <<<'content' | head -5` now succeed instead of being skipped. No public API change — `applyBashFixups(command)` returns the same `{ command, stripped }` shape.
78
+
79
+ ### Fixed
80
+
81
+ - Fixed bash command fixups to remove a redundant standalone trailing `2>&1` redirect when no other pipe or redirection remains
82
+ - Fixed command-fixup notices to list all stripped segments instead of reporting only one
83
+ - Fixed summarized `read` output stalling agents on elided regions by appending an explicit footer like `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`. The footer fires whenever the structural summarizer elided at least one span, so the model gets a concrete recovery selector instead of having to guess from a bare `...` / `{ .. }` marker. Surfaces `elidedLines` on `ReadToolDetails.summary` alongside the existing `elidedSpans`. ([#1046](https://github.com/can1357/oh-my-pi/issues/1046))
84
+ - Updated the `read` tool prompt to describe the new elision footer and instruct the model to follow `:raw` (or an explicit line range) when the elided body is actually needed, rather than guessing.
85
+ - Fixed plugin extensions failing to load when their `peerDependencies` reference internal `pi-*` packages under any scope other than `@mariozechner` (e.g. `Cannot find module '@earendil-works/pi-tui'` from `@juicesharp/rpiv-ask-user-question`, or `Cannot find module '@oh-my-pi/pi-utils'` from `@oh-my-pi/swarm-extension`). The legacy-pi specifier shim now treats `@mariozechner`, `@earendil-works`, **and** the canonical `@oh-my-pi` itself as aliases for the same set of bundled in-process packages (`pi-agent-core`, `pi-ai`, `pi-coding-agent`, `pi-natives`, `pi-tui`, `pi-utils`), and additionally rewrites the upstream-only `pi-ai/oauth` subpath onto our `pi-ai/utils/oauth` layout. Restored the `Key` runtime helper export on `@oh-my-pi/pi-tui` to match upstream — plugins using `Key.enter` / `Key.ctrl("c")` (e.g. `@plannotator/pi-extension`, `@juicesharp/rpiv-ask-user-question`) no longer fail with `Export named 'Key' not found`. End-to-end verified against `@juicesharp/rpiv-ask-user-question`, `@oh-my-pi/swarm-extension`, and `@plannotator/pi-extension` — each now loads cleanly with all of its tools/commands/handlers registered. Plugins importing any of those scopes are remapped to the omp binary's own copy at load time, so peer deps are no longer dragged in from npm and there is exactly one module instance per package regardless of which scope name the plugin's manifest happened to declare.
86
+ - Fixed `omp commit` hanging after a successful commit instead of returning to the shell. The command now mirrors the `runPrintMode` exit pattern and calls `postmortem.quit(0)` once the pipeline resolves so lingering HTTP/2 keep-alive sockets, the Settings autosave timer, and other AgentSession background handles don't keep the event loop pinned. ([#1041](https://github.com/can1357/oh-my-pi/issues/1041))
87
+ - Fixed hashline payload parsing to silently treat truly-blank lines as empty `~`-prefixed payload lines when more payload follows in the same run. The previous behavior broke at the blank ("payload line has no preceding +, <, or = operation.") even though the intent is obvious — the only ambiguity is between in-payload blanks and end-of-section blanks, and a one-line lookahead resolves it: blanks that precede a non-payload op still end the run cleanly as section separators. Recovers the common case of forgetting the leading separator on a blank inserted line without changing how trailing blanks between ops behave.
88
+ - Rewrote the hashline edit prompt examples to use an ASCII-only `TITLE = "Mr"` → `"Mrs"` / `"Dr"` motif instead of the previous `" • "` and `"·"` separators. Some agents had been copying the middle-dot literal characters into real edits as if they were format scaffolding (e.g. emitting payload lines like `~ ·`), since the demo inserts were near-twins of the existing string. The new example keeps every original op shape (single-line replace, multiline replace, insert AFTER/BEFORE, append, delete, blank, plus both anti-patterns) but uses content that is obviously domain-specific and clearly distinct from any payload separator. Pure prompt change; no parser, schema, or runtime behavior is affected.
89
+ - Fixed startup fallback-chain validation to recognize cached runtime-discovered standard provider models, including Ollama Cloud models listed by `--list-models`, so `retry.fallbackChains` no longer warns that valid `ollama-cloud/<model>` selectors are unknown. ([#1052](https://github.com/can1357/oh-my-pi/issues/1052))
90
+ - Fixed `discoverAgents()` ignoring `disabledProviders` for the `claude-plugins` provider. Plugin roots from `~/.claude/plugins/` were scanned unconditionally, so agents from Claude Code marketplace plugins continued to appear in `/agents` and the Agent Control Center even when `disabledProviders: [claude-plugins]` was set. The discovery path now checks `isProviderEnabled("claude-plugins")` before calling `listClaudePluginRoots()`, matching how every other capability respects the disabled-providers set. ([#1075](https://github.com/can1357/oh-my-pi/issues/1075))
91
+
92
+ ### Fixed
93
+
94
+ - Fixed `$env:VAR` PowerShell variables being mangled on Windows when commands invoked PowerShell as a subprocess (e.g. `powershell -Command "Write-Host $env:SystemRoot"`). Brush-core applied POSIX parameter expansion to `$env` before spawning the child, leaving a dangling `:NAME`. The fix lives in `pi-shell` at env-var application time: every brush session now defines `env=$env` as an internal shell variable so `$env:NAME` expands to the literal `$env:NAME` token that PowerShell expects. The fallback is not exported, only influences brush's own expansion, and is shadowed by any user assignment to `env` (e.g. `env=prod; echo "$env:8080"` still prints `prod:8080`), so the POSIX bash contract is preserved. ([#1079](https://github.com/can1357/oh-my-pi/issues/1079))
95
+
96
+
5
97
  ## [15.0.1] - 2026-05-14
6
98
  ### Breaking Changes
7
99
 
@@ -15,6 +107,7 @@
15
107
  - Added per-line column cap shared across streaming tool outputs (`bash`, `ssh`, `python`, `js eval`) and the `read` tool. Lines wider than `tools.outputMaxColumns` bytes (default **768**) are ellipsis-truncated at write time and remaining bytes up to the next `\n` are dropped — bounded memory even on multi-MB single-line outputs (e.g. `cat /dev/urandom`). The cap lives on `OutputSink` as the new `maxColumns` option, persists state across chunk boundaries so split-mid-line writes still respect the budget, and exposes `columnDroppedBytes` / `columnTruncatedLines` on `OutputSummary`. Middle-elision byte math subtracts column drops so the "elided from middle" count stays honest. `read` reuses the same setting but trims its already-collected lines via `truncateLine`. Skipped when the read selector is `:raw`. The artifact file (`artifact://<id>`) keeps the full uncapped stream. Set `tools.outputMaxColumns = 0` to disable.
16
108
  - Added Bun HTTP/2 fetch opt-in. Dev scripts (`bun run dev`, `bun run stats`) now pass `bun --experimental-http2-fetch` so every `fetch()` advertises `h2` in the TLS ALPN list and falls back to HTTP/1.1 when the server doesn't select it. Multiplexing collapses parallel requests to the same origin onto one TLS connection. For the installed `omp` binary, export `BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT=1` in your shell to enable the same behavior (the flag has to be set before Bun starts; `process.env` from inside JS is too late). Requires Bun **1.3.14**.
17
109
  - Added per-subagent cost display (`$X.XX` in the task progress tree and the session-observer stats line). Cost is accumulated incrementally from `message_end` events and shown only when non-zero, using the `statusLineCost` theme color. Providers that do not report per-turn cost data (e.g. subscription/OAuth usage) continue to show nothing.
110
+ - Added ACP elicitation bridge so skills/extensions calling `select`, `confirm`, or `input` on the extension UI context now produce real `unstable_createElicitation` form requests to the ACP client (rather than always resolving to `undefined` / `false`). The `acpExtensionUiContext` constant is promoted to `createAcpExtensionUiContext(connection, getSessionId, clientCapabilities)` — invoked once per session inside `#configureExtensions`, with `getSessionId: () => string` so the live `record.session.sessionId` is read on every elicitation (the underlying id mutates when an extension command calls `ctx.newSession` / `ctx.switchSession`). Each method maps to a single-property `value` schema: `select` → `{type: "string", enum}`, `confirm` → `{type: "boolean"}` (joined `title` + `message` when the trimmed message is non-empty; otherwise just `title`), `input` → `{type: "string", description: placeholder?}` (ACP has no `placeholder` field on `StringPropertySchema`; empty / whitespace-only placeholders are treated as absent). `accept` responses narrow the returned `ElicitationContentValue` back to the method's declared type with a runtime `typeof` guard; `decline` / `cancel` / transport failures fall back to the prior stub return values. `dialogOptions.signal` is honored: an already-aborted signal short-circuits before any SDK round-trip, and an abort mid-flight races against the elicitation so the caller's promise resolves to the stub fallback (the ACP request itself keeps running on the client side — the SDK exposes no form-mode cancel surface; `unstable_completeElicitation` is URL-mode only — matching the in-flight pattern used by `requestRpcEditor`). `dialogOptions.timeout` is honored on parity with `RpcExtensionUIContext`: when the timer fires before the client responds, `onTimeout` is invoked and the caller resolves to the stub fallback. A throwing `onTimeout` is caught and logged (`logger.warn`) so the elicitation promise still settles. Late SDK rejections that arrive after abort/timeout are dropped silently to keep operator logs clean; transport failures still emit `logger.warn` with `{ sessionId, method, error }`. Calls are skipped when the client did not advertise `clientCapabilities.elicitation.form` during `initialize`, so non-elicitation clients are unaffected. `createAcpExtensionUiContext` is exported for tests.
18
111
 
19
112
  ### Changed
20
113
 
@@ -56,7 +149,7 @@
56
149
  - 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.
57
150
  - 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.
58
151
  - 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.
59
- - 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.
152
+ - Added `CompactionCancelledError` typed sentinel and `CompactionOutcome` (`"ok" | "cancelled" | "failed"`) return type to `@oh-my-pi/pi-agent-core/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.
60
153
  - 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.
61
154
  - Added `omp acp` subcommand for launching as an ACP (Agent Client Protocol) server over stdio
62
155
  - Added explicit `type` discriminators to ACP `initialize` auth methods, including a `terminal` setup method gated on `clientCapabilities.auth.terminal`
@@ -47,7 +47,6 @@ See [docs/custom-tools.md](../../docs/custom-tools.md) for full documentation.
47
47
  **Factory pattern:**
48
48
 
49
49
  ```typescript
50
- import { Type } from "@sinclair/typebox";
51
50
  import { StringEnum } from "@oh-my-pi/pi-ai";
52
51
  import { Text } from "@oh-my-pi/pi-tui";
53
52
  import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
@@ -56,7 +55,7 @@ const factory: CustomToolFactory = (pi) => ({
56
55
  name: "my_tool",
57
56
  label: "My Tool",
58
57
  description: "Tool description for LLM",
59
- parameters: Type.Object({
58
+ parameters: pi.zod.object({
60
59
  action: StringEnum(["list", "add"] as const),
61
60
  }),
62
61
 
@@ -78,6 +77,8 @@ const factory: CustomToolFactory = (pi) => ({
78
77
  export default factory;
79
78
  ```
80
79
 
80
+ **Legacy:** `parameters: pi.typebox.Type.Object({ ... })` still works; the injected `typebox` is a small Zod-backed shim, and schemas flow through the same Zod pipeline as `pi.zod` schemas.
81
+
81
82
  **Custom rendering:**
82
83
 
83
84
  ```typescript
@@ -96,14 +97,17 @@ renderResult(result, { expanded, isPartial }, theme) {
96
97
  },
97
98
  ```
98
99
 
99
- **Use StringEnum for string parameters** (required for Google API compatibility):
100
+ **Use `StringEnum` for discriminated string tool args** (required for Google API compatibility):
100
101
 
101
102
  ```typescript
102
103
  import { StringEnum } from "@oh-my-pi/pi-ai";
103
104
 
104
- // Good
105
- action: StringEnum(["list", "add"] as const);
105
+ const { z } = pi.zod;
106
+
107
+ // Good — Google-safe enum wiring
108
+ parameters: z.object({
109
+ action: StringEnum(["list", "add"] as const),
110
+ });
106
111
 
107
- // Bad - doesn't work with Google
108
- action: Type.Union([Type.Literal("list"), Type.Literal("add")]);
112
+ // Avoid raw union-of-literals patterns that don't degrade well for strict JSON Schema providers
109
113
  ```
@@ -4,8 +4,8 @@ const factory: CustomToolFactory = pi => ({
4
4
  name: "hello",
5
5
  label: "Hello",
6
6
  description: "A simple greeting tool",
7
- parameters: pi.typebox.Type.Object({
8
- name: pi.typebox.Type.String({ description: "Name to greet" }),
7
+ parameters: pi.zod.object({
8
+ name: pi.zod.string().describe("Name to greet"),
9
9
  }),
10
10
 
11
11
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
@@ -71,9 +71,10 @@ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
71
71
 
72
72
  ```typescript
73
73
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
74
- import { Type } from "@sinclair/typebox";
75
74
 
76
75
  export default function (pi: ExtensionAPI) {
76
+ const z = pi.zod;
77
+
77
78
  // Subscribe to lifecycle events
78
79
  pi.on("tool_call", async (event, ctx) => {
79
80
  if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
@@ -87,8 +88,8 @@ export default function (pi: ExtensionAPI) {
87
88
  name: "greet",
88
89
  label: "Greeting",
89
90
  description: "Generate a greeting",
90
- parameters: Type.Object({
91
- name: Type.String({ description: "Name to greet" }),
91
+ parameters: z.object({
92
+ name: z.string().describe("Name to greet"),
92
93
  }),
93
94
  async execute(toolCallId, params, onUpdate, ctx, signal) {
94
95
  return {
@@ -108,18 +109,28 @@ export default function (pi: ExtensionAPI) {
108
109
  }
109
110
  ```
110
111
 
112
+ **Legacy TypeBox-style schemas** (`pi.typebox`) remain available for older extensions and are backed by a tiny Zod-shim — prefer `pi.zod` directly for new code.
113
+
114
+ ```typescript
115
+ const { Type } = pi.typebox;
116
+ parameters: Type.Object({ name: Type.String() });
117
+ ```
118
+
111
119
  ## Key Patterns
112
120
 
113
- **Use StringEnum for string parameters** (required for Google API compatibility):
121
+ **Use `StringEnum` for discriminated string tool args** (required for Google API compatibility):
114
122
 
115
123
  ```typescript
116
124
  import { StringEnum } from "@oh-my-pi/pi-ai";
117
125
 
118
- // Good
119
- action: StringEnum(["list", "add"] as const);
126
+ const { z } = pi.zod;
127
+
128
+ // Good — Google-safe enum wiring
129
+ parameters: z.object({
130
+ action: StringEnum(["list", "add"] as const),
131
+ });
120
132
 
121
- // Bad - doesn't work with Google
122
- action: Type.Union([Type.Literal("list"), Type.Literal("add")]);
133
+ // Avoid raw union-of-literals patterns that don't degrade well for strict JSON Schema providers
123
134
  ```
124
135
 
125
136
  **State persistence via details:**
@@ -1,39 +1,35 @@
1
1
  /**
2
2
  * API Demo Extension
3
3
  *
4
- * Demonstrates using ExtensionAPI's logger, typebox, and pi module access.
4
+ * Demonstrates using ExtensionAPI's logger, injected `pi.zod`, and pi module access.
5
5
  * These features are now exposed directly on the ExtensionAPI, matching
6
6
  * the CustomToolAPI interface.
7
7
  */
8
8
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
9
9
 
10
10
  export default function (pi: ExtensionAPI) {
11
- // 1. Access TypeBox directly from pi.typebox (no separate import needed)
12
- const { Type } = pi.typebox;
11
+ const { z } = pi.zod;
13
12
 
14
- // 2. Access the logger for debugging
15
- pi.logger.debug("API demo extension loaded");
16
-
17
- // 3. Register a tool that uses all three API features
18
- // Import StringEnum from typebox helpers
13
+ // Access shared schema helpers from package exports (e.g. StringEnum for Google-safe enums)
19
14
  const { StringEnum } = pi.pi;
20
15
 
16
+ // Access the logger for debugging
17
+ pi.logger.debug("API demo extension loaded");
18
+
21
19
  pi.registerTool({
22
20
  name: "api_demo",
23
21
  label: "API Demo",
24
- description: "Demonstrates ExtensionAPI capabilities: logger, typebox, and pi module access",
25
- parameters: Type.Object({
26
- message: Type.String({ description: "Test message" }),
27
- logLevel: Type.Optional(
28
- StringEnum(["error", "warn", "debug"], {
29
- description: "Log level to use",
30
- default: "debug",
31
- }),
32
- ),
22
+ description: "Demonstrates ExtensionAPI capabilities: logger, zod, and pi module access",
23
+ parameters: z.object({
24
+ message: z.string().describe("Test message"),
25
+ logLevel: StringEnum(["error", "warn", "debug"], {
26
+ description: "Log level to use",
27
+ default: "debug",
28
+ }),
33
29
  }),
34
30
 
35
31
  async execute(_toolCallId, params, _onUpdate, ctx, _signal) {
36
- const { message, logLevel = "debug" } = params as { message: string; logLevel?: "error" | "warn" | "debug" };
32
+ const { message, logLevel } = params;
37
33
 
38
34
  // Use logger at specified level
39
35
  pi.logger[logLevel]("API demo tool executed", { message, logLevel });
@@ -58,7 +54,7 @@ export default function (pi: ExtensionAPI) {
58
54
  ``,
59
55
  `Features demonstrated:`,
60
56
  `1. ✓ Logger access via pi.logger`,
61
- `2. ✓ TypeBox access via pi.typebox`,
57
+ `2. ✓ Zod access via pi.zod`,
62
58
  `3. ✓ Pi module access via pi.pi`,
63
59
  ``,
64
60
  `Context:`,
@@ -1,24 +1,23 @@
1
1
  /**
2
2
  * Hello Tool - Minimal custom tool example
3
3
  *
4
- * Demonstrates using ExtensionAPI's logger, typebox, and pi module access.
4
+ * Demonstrates using ExtensionAPI's logger, injected `pi.zod`, and pi module access.
5
5
  */
6
6
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
7
7
 
8
8
  export default function (pi: ExtensionAPI) {
9
- // Access TypeBox via pi.typebox (no need to import separately)
10
- const { Type } = pi.typebox;
9
+ const { z } = pi.zod;
11
10
 
12
11
  pi.registerTool({
13
12
  name: "hello",
14
13
  label: "Hello",
15
14
  description: "A simple greeting tool",
16
- parameters: Type.Object({
17
- name: Type.String({ description: "Name to greet" }),
15
+ parameters: z.object({
16
+ name: z.string().describe("Name to greet"),
18
17
  }),
19
18
 
20
19
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
21
- const { name } = params as { name: string };
20
+ const { name } = params;
22
21
 
23
22
  // Use logger for debugging
24
23
  pi.logger.debug("Hello tool executed", { name });
@@ -426,7 +426,7 @@ Execute each step in order.`,
426
426
 
427
427
  // Extract todos from last message
428
428
  const messages = event.messages;
429
- const lastAssistant = [...messages].reverse().find(m => m.role === "assistant");
429
+ const lastAssistant = messages.findLast(m => m.role === "assistant");
430
430
  if (lastAssistant && Array.isArray(lastAssistant.content)) {
431
431
  const textContent = lastAssistant.content
432
432
  .filter(
@@ -5,10 +5,11 @@
5
5
  * tool that queues a follow-up command to trigger reload.
6
6
  */
7
7
 
8
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
- import { Type } from "@sinclair/typebox";
8
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
10
9
 
11
10
  export default function (pi: ExtensionAPI) {
11
+ const { z } = pi.zod;
12
+
12
13
  // Command entrypoint for reload.
13
14
  // Treat reload as terminal for this handler.
14
15
  pi.registerCommand("reload-runtime", {
@@ -25,7 +26,7 @@ export default function (pi: ExtensionAPI) {
25
26
  name: "reload_runtime",
26
27
  label: "Reload Runtime",
27
28
  description: "Reload extensions, skills, prompts, and themes",
28
- parameters: Type.Object({}),
29
+ parameters: z.object({}),
29
30
  async execute() {
30
31
  pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
31
32
  return {
@@ -5,17 +5,18 @@
5
5
  * Requires: npm install in this directory
6
6
  */
7
7
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
8
- import { Type } from "@sinclair/typebox";
9
8
  import ms from "ms";
10
9
 
11
10
  export default function (pi: ExtensionAPI) {
11
+ const { z } = pi.zod;
12
+
12
13
  // Register a tool that uses ms
13
14
  pi.registerTool({
14
15
  name: "parse_duration",
15
16
  label: "Parse Duration",
16
17
  description: "Parse a human-readable duration string (e.g., '2 days', '1h', '5m') to milliseconds",
17
- parameters: Type.Object({
18
- duration: Type.String({ description: "Duration string like '2 days', '1h', '5m'" }),
18
+ parameters: z.object({
19
+ duration: z.string().describe("Duration string like '2 days', '1h', '5m'"),
19
20
  }),
20
21
  execute: async (_toolCallId, params) => {
21
22
  const result = ms(params.duration as ms.StringValue);
@@ -41,6 +41,8 @@ console.log();
41
41
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
42
42
 
43
43
  export default function (pi: ExtensionAPI) {
44
+ const { z } = pi.zod;
45
+
44
46
  pi.on("agent_start", async () => {
45
47
  console.log("[Extension] Agent starting");
46
48
  });
@@ -60,8 +62,8 @@ export default function (pi: ExtensionAPI) {
60
62
  name: "my_tool",
61
63
  label: "My Tool",
62
64
  description: "Does something useful",
63
- parameters: Type.Object({
64
- input: Type.String(),
65
+ parameters: z.object({
66
+ input: z.string(),
65
67
  }),
66
68
  execute: async (_toolCallId, params, _onUpdate, _ctx, _signal) => ({
67
69
  content: [{ type: "text", text: \`Processed: \${params.input}\` }],
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.0.1",
4
+ "version": "15.1.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
- "homepage": "https://github.com/can1357/oh-my-pi",
6
+ "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
8
8
  "contributors": [
9
9
  "Mario Zechner"
@@ -47,17 +47,15 @@
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": "15.0.1",
51
- "@oh-my-pi/pi-agent-core": "15.0.1",
52
- "@oh-my-pi/pi-ai": "15.0.1",
53
- "@oh-my-pi/pi-natives": "15.0.1",
54
- "@oh-my-pi/pi-tui": "15.0.1",
55
- "@oh-my-pi/pi-utils": "15.0.1",
50
+ "@oh-my-pi/omp-stats": "15.1.0",
51
+ "@oh-my-pi/pi-agent-core": "15.1.0",
52
+ "@oh-my-pi/pi-ai": "15.1.0",
53
+ "@oh-my-pi/pi-natives": "15.1.0",
54
+ "@oh-my-pi/pi-tui": "15.1.0",
55
+ "@oh-my-pi/pi-utils": "15.1.0",
56
56
  "@puppeteer/browsers": "^2.13.0",
57
- "@sinclair/typebox": "^0.34.49",
58
57
  "@types/turndown": "5.0.6",
59
58
  "@xterm/headless": "^6.0.0",
60
- "ajv": "^8.20.0",
61
59
  "chalk": "^5.6.2",
62
60
  "diff": "^9.0.0",
63
61
  "fflate": "0.8.2",
@@ -473,14 +471,6 @@
473
471
  "types": "./src/session/*.ts",
474
472
  "import": "./src/session/*.ts"
475
473
  },
476
- "./session/compaction": {
477
- "types": "./src/session/compaction/index.ts",
478
- "import": "./src/session/compaction/index.ts"
479
- },
480
- "./session/compaction/*": {
481
- "types": "./src/session/compaction/*.ts",
482
- "import": "./src/session/compaction/*.ts"
483
- },
484
474
  "./slash-commands/*": {
485
475
  "types": "./src/slash-commands/*.ts",
486
476
  "import": "./src/slash-commands/*.ts"
@@ -1,7 +1,7 @@
1
1
  import * as path from "node:path";
2
- import { StringEnum } from "@oh-my-pi/pi-ai";
2
+
3
3
  import { Text } from "@oh-my-pi/pi-tui";
4
- import { Type } from "@sinclair/typebox";
4
+ import * as z from "zod/v4";
5
5
  import type { ToolDefinition } from "../../extensibility/extensions";
6
6
  import type { Theme } from "../../modes/theme/theme";
7
7
  import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
@@ -16,46 +16,43 @@ export const HARNESS_FILENAME = "autoresearch.sh";
16
16
  export const DEFAULT_HARNESS_COMMAND = `bash ${HARNESS_FILENAME}`;
17
17
  const HARNESS_COMMIT_TITLE = "autoresearch: harness setup";
18
18
 
19
- const initExperimentSchema = Type.Object({
20
- name: Type.String({ description: "Human-readable experiment name." }),
21
- goal: Type.Optional(Type.String({ description: "Free-form description of what this session optimizes." })),
22
- primary_metric: Type.String({
23
- description:
19
+ const initExperimentSchema = z.object({
20
+ name: z.string().describe("Human-readable experiment name."),
21
+ goal: z.string().describe("Free-form description of what this session optimizes.").optional(),
22
+ primary_metric: z
23
+ .string()
24
+ .describe(
24
25
  "Primary metric name shown in the dashboard. Match the `METRIC <name>=<value>` lines printed by the benchmark.",
25
- }),
26
- metric_unit: Type.Optional(
27
- Type.String({ description: "Unit for the primary metric (e.g. ms, µs, mb). Empty when unitless." }),
28
- ),
29
- direction: Type.Optional(
30
- StringEnum(["lower", "higher"], { description: "Whether lower or higher values are better. Defaults to lower." }),
31
- ),
32
- secondary_metrics: Type.Optional(
33
- Type.Array(Type.String(), {
34
- description: "Names of secondary metrics tracked alongside the primary metric.",
35
- }),
36
- ),
37
- scope_paths: Type.Optional(
38
- Type.Array(Type.String(), {
39
- description:
40
- "Files or directories the agent expects to modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
41
- }),
42
- ),
43
- off_limits: Type.Optional(
44
- Type.Array(Type.String(), {
45
- description:
46
- "Paths the agent SHOULD NOT modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
47
- }),
48
- ),
49
- constraints: Type.Optional(
50
- Type.Array(Type.String(), { description: "Free-form constraints (e.g. 'no api break')." }),
51
- ),
52
- max_iterations: Type.Optional(Type.Number({ description: "Soft cap on iterations per segment. Optional." })),
53
- new_segment: Type.Optional(
54
- Type.Boolean({
55
- description:
56
- "When true, bump to a new segment even when an active session exists. New baselines and best-metric reset.",
57
- }),
58
- ),
26
+ ),
27
+ metric_unit: z.string().describe("Unit for the primary metric (e.g. ms, µs, mb). Empty when unitless.").optional(),
28
+ direction: z
29
+ .enum(["lower", "higher"] as const)
30
+ .describe("Whether lower or higher values are better. Defaults to lower.")
31
+ .optional(),
32
+ secondary_metrics: z
33
+ .array(z.string())
34
+ .describe("Names of secondary metrics tracked alongside the primary metric.")
35
+ .optional(),
36
+ scope_paths: z
37
+ .array(z.string())
38
+ .describe(
39
+ "Files or directories the agent expects to modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
40
+ )
41
+ .optional(),
42
+ off_limits: z
43
+ .array(z.string())
44
+ .describe(
45
+ "Paths the agent SHOULD NOT modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
46
+ )
47
+ .optional(),
48
+ constraints: z.array(z.string()).describe("Free-form constraints (e.g. 'no api break').").optional(),
49
+ max_iterations: z.number().describe("Soft cap on iterations per segment. Optional.").optional(),
50
+ new_segment: z
51
+ .boolean()
52
+ .describe(
53
+ "When true, bump to a new segment even when an active session exists. New baselines and best-metric reset.",
54
+ )
55
+ .optional(),
59
56
  });
60
57
 
61
58
  interface InitExperimentDetails {