@oh-my-pi/pi-coding-agent 15.10.8 → 15.10.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/dist/types/config/model-registry.d.ts +13 -0
  3. package/dist/types/config/settings-schema.d.ts +0 -9
  4. package/dist/types/debug/terminal-info.d.ts +0 -1
  5. package/dist/types/extensibility/custom-tools/loader.d.ts +22 -3
  6. package/dist/types/extensibility/extensions/index.d.ts +1 -1
  7. package/dist/types/extensibility/extensions/loader.d.ts +17 -1
  8. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
  9. package/dist/types/mcp/transports/stdio.d.ts +12 -0
  10. package/dist/types/modes/components/custom-editor.d.ts +3 -2
  11. package/dist/types/modes/components/transcript-container.d.ts +12 -26
  12. package/dist/types/sdk.d.ts +42 -2
  13. package/dist/types/task/discovery.d.ts +1 -2
  14. package/dist/types/task/executor.d.ts +16 -0
  15. package/dist/types/tiny/title-client.d.ts +1 -1
  16. package/dist/types/tools/index.d.ts +17 -0
  17. package/dist/types/tools/todo.d.ts +2 -0
  18. package/dist/types/tui/hyperlink.d.ts +8 -0
  19. package/package.json +9 -9
  20. package/src/cli/list-models.ts +5 -11
  21. package/src/config/model-registry.ts +91 -20
  22. package/src/config/settings-schema.ts +0 -10
  23. package/src/debug/terminal-info.ts +0 -3
  24. package/src/edit/diff.ts +48 -15
  25. package/src/eval/js/shared/rewrite-imports.ts +9 -1
  26. package/src/extensibility/custom-tools/loader.ts +43 -19
  27. package/src/extensibility/extensions/index.ts +1 -0
  28. package/src/extensibility/extensions/loader.ts +29 -6
  29. package/src/extensibility/plugins/legacy-pi-compat.ts +30 -6
  30. package/src/internal-urls/docs-index.generated.ts +4 -4
  31. package/src/mcp/transports/stdio.ts +139 -3
  32. package/src/modes/components/custom-editor.ts +69 -9
  33. package/src/modes/components/model-selector.ts +62 -52
  34. package/src/modes/components/transcript-container.ts +204 -125
  35. package/src/modes/controllers/event-controller.ts +0 -45
  36. package/src/modes/controllers/input-controller.ts +5 -5
  37. package/src/modes/controllers/mcp-command-controller.ts +2 -2
  38. package/src/modes/controllers/selector-controller.ts +0 -4
  39. package/src/modes/interactive-mode.ts +2 -10
  40. package/src/prompts/system/system-prompt.md +3 -3
  41. package/src/prompts/tools/bash.md +3 -3
  42. package/src/prompts/tools/todo.md +5 -1
  43. package/src/sdk.ts +138 -56
  44. package/src/ssh/ssh-executor.ts +60 -4
  45. package/src/task/discovery.ts +17 -24
  46. package/src/task/executor.ts +19 -0
  47. package/src/task/index.ts +4 -0
  48. package/src/tiny/title-client.ts +6 -3
  49. package/src/tools/index.ts +17 -0
  50. package/src/tools/todo.ts +16 -7
  51. package/src/tui/hyperlink.ts +27 -3
  52. package/src/web/search/providers/anthropic.ts +8 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.10.10] - 2026-06-09
6
+
7
+ ### Added
8
+
9
+ - Added a read-only `view` op to the `todo` tool that echoes the current list without mutating state, so the agent can recover exact task text instead of guessing it from memory.
10
+
11
+ ### Changed
12
+
13
+ - Rewrote the bash tool's coreutils guidance (tool prompt and system prompt) around an explicit litmus: pipelines that compute a new fact (`wc -l`, `sort | uniq -c`, `comm`, `diff`) are legitimate bash, while commands that merely move, page, or trim bytes a dedicated tool can fetch remain banned — output trimming destroys data the `artifact://` capture would have saved.
14
+
15
+ ### Fixed
16
+
17
+ - Fixed the model selector dropping an immediate Enter when cached models were available but the selector's offline refresh was still pending.
18
+ - Fixed dynamic `import(...)` inside functions passed to the browser tool's `tab.evaluate`/`page.evaluate` failing with `__omp_import__ is not defined`. The eval/browser JS runtime rewrites dynamic-import callees to the worker-injected `__omp_import__` helper, but puppeteer serializes evaluate callbacks with `Function.prototype.toString()` and re-runs them inside the page, where the helper does not exist. The rewriter now substitutes a guarded shim that falls back to native dynamic import when the helper is absent, so serialized code works in the page realm while in-worker imports keep resolving against the session cwd.
19
+ - Transcript block freezing is now unconditional instead of gated on ED3-risk terminal detection: every finalized block replays its frozen snapshot once it crosses out of the live region, on all terminals including Windows, because the rewritten renderer's committed scrollback is immutable everywhere. Still-mutating blocks (pending tools, streaming messages, async thinking renderers) anchor the live region and keep repainting until they finalize, which structurally fixes stale/duplicated output from late async expansions ([#1823](https://github.com/can1357/oh-my-pi/issues/1823)).
20
+ - Fixed the edit tool's post-edit diff preview occasionally echoing a context line twice with out-of-order numbering. Block-boundary context injection classified space-prefixed diff rows as old-file-only, so an unchanged line sitting in a net-offset region (old N / new N+k) was missing from the new file's visibility window; `findBlockContextLines` then re-surfaced it under its post-edit number and the row was spliced in after the adjacent change run. New-file boundary lines are now translated back to pre-edit numbers (the compact-preview renumbering contract) and merged into a single old-numbered insertion pass — also fixing closers below a net-offset edit being dropped or renumbered incorrectly.
21
+ - Fixed the Anthropic web-search provider claiming the Claude Code identity on API-key requests: the CC billing header + system instruction were injected whenever the model wasn't Haiku 3.5, regardless of auth mode. Injection is now OAuth-gated like the streaming path, and OAuth search requests patch the billing header's `cch` attestation (via `wrapFetchForCch`) instead of shipping the `cch=00000` placeholder.
22
+ - Fixed long streamed content appearing cut off mid-run: scrolled-off rows were erased from the viewport without ever being appended to terminal history. The transcript's commit boundary (`deriveLiveCommitState`) was all-or-nothing per block — one perpetually rewriting row (a task tool's ticking progress tree, per-agent cost/tool counters, spinner stats) suspended scrollback commits for the entire block, so once the block outgrew the viewport its static head (e.g. a task's prompt/context markdown) was neither committed nor on screen until the tool sealed, and was lost outright if the session ended mid-run. A stable-prefix ratchet now promotes leading rows that stayed visibly identical for a full 30-frame window as commit-safe, so the settled head reaches native scrollback while only the genuinely volatile tail stays deferred; a rewrite above the promoted run retreats the boundary and the engine audit recommits (duplication, never loss).
23
+ - Fixed local tiny-title worker stdout/stderr leaking raw native model output such as `</title>` and cache/status lines into the interactive TUI scrollback ([#2206](https://github.com/can1357/oh-my-pi/issues/2206)).
24
+ - Fixed task-agent discovery advertising Claude Code custom agents from `.claude/agents/*.md` as OMP subagents; direct task-agent discovery now only loads OMP-native `.omp` agent roots, while Claude marketplace plugin agents keep their existing provider path ([#2209](https://github.com/can1357/oh-my-pi/issues/2209)).
25
+
26
+ ### Removed
27
+
28
+ - Removed the `clearOnShrink` setting and its `PI_CLEAR_ON_SHRINK` environment variable: the rewritten renderer always clears shrunken rows exactly, so the flicker/perf tradeoff the setting controlled no longer exists. Existing config entries are ignored.
29
+ - Removed the prompt-submit native-scrollback reconciliation checkpoint and the eager streaming render mode from the interactive controllers — the renderer's append-only contract made both obsolete.
30
+
31
+ ## [15.10.9] - 2026-06-09
32
+
33
+ ### Fixed
34
+
35
+ - Fixed streaming thinking (and other styled assistant content) vanishing from native scrollback once it scrolled past the viewport top during a foreground turn. The transcript's append-only commit detector compared raw row bytes, so a styled paragraph wrapping onto a new row (the span-closing SGR and width padding move while the visible cells stay identical) or a streamed token pushing the last word down a line flagged the block as permanently volatile — the commit boundary froze and every later row that crossed the viewport top was committed nowhere. Rows are now compared by visible content, a wrap-shrink of the in-flight bottom line counts as append-only, and a genuine one-off interior rewrite only suspends commits until the block re-earns append-only (30 clean frames), after which the pinned emitter backfills the stalled gap contiguously. Periodically rewriting blocks (spinners, collapsing tool previews) never re-earn and stay deferred.
36
+
37
+ - Fixed bracketed pastes containing multiple image file paths so each image is attached in order instead of treating the whole paste as one unreadable path.
38
+
39
+ - Fixed MCP OAuth fallback prompts so the "Click here to authorize" label emits an auth-safe terminal hyperlink even when hyperlink auto-detection is unavailable, keeping non-browser MCP setup usable ([#2196](https://github.com/can1357/oh-my-pi/issues/2196)).
40
+ - Fixed `task`-spawned subagents repeating filesystem scans the parent had already completed. `ExecutorOptions` and the `createAgentSession()` call inside `runSubprocess()` did not forward `rules`, the discovered extension paths, or the discovered `.omp/tools/` paths, so each subagent re-ran `loadCapability<Rule>()`, `discoverAndLoadExtensions()`, and the full `.omp/tools/` walk. The toolsession now caches `session.rules`, `session.extensionPaths`, and `session.customToolPaths`; `runSubprocess()` threads them through; and `createAgentSession()` accepts new `preloadedExtensionPaths` and `preloadedCustomToolPaths` options backed by new exported `discoverExtensionPaths()` and `discoverCustomToolPaths()` helpers. Crucially, only path lists are forwarded — never loaded instances. Each session rebuilds its own `Extension` and `LoadedCustomTool` objects so the per-session `ExtensionAPI`/`CustomToolAPI` (cwd, eventBus, runtime, exec, pushPendingAction, UI) targets the right session; forwarding loaded instances would have routed extension handlers and custom-tool execution back through the parent. The CLI's `preloadedExtensions` short-circuit is preserved for same-process reuse and now shallow-clones the caller's `extensions` array so inline-extension augmentation (autoresearch + custom-tools wrapper) cannot bleed back into it ([#2190](https://github.com/can1357/oh-my-pi/issues/2190)).
41
+ - Fixed SSH tool cancellation hanging behind OpenSSH ControlMaster streams that stayed open after an Esc/user interrupt ([#2180](https://github.com/can1357/oh-my-pi/issues/2180)).
42
+ - Fixed Windows stdio MCP servers launched through PATH shims such as `codegraph.cmd` so bare commands like `codegraph` resolve via `PATHEXT` before spawn ([#2174](https://github.com/can1357/oh-my-pi/issues/2174)).
43
+ - Fixed compiled-binary extensions failing to load `@oh-my-pi/pi-*` packages when `bun --compile` quietly dropped one of the extra entrypoints (observed on macOS arm64 release builds): the legacy-pi compat shim's package-root override branch returned the bunfs path without checking the target was present, so the rewrite emitted a `file://` URL to a missing module and the #1216 fallback (scoped to the throwing `getResolvedSpecifier` path) never ran. Override targets are now validated against the on-disk filesystem at module init, missing entries are dropped, and resolution falls through to canonical lookup so Bun resolves the import from the extension's own `node_modules` ([#2168](https://github.com/can1357/oh-my-pi/issues/2168)).
44
+
5
45
  ## [15.10.8] - 2026-06-09
6
46
 
7
47
  ### Added
@@ -9796,4 +9836,4 @@ Initial public release.
9796
9836
  - Git branch display in footer
9797
9837
  - Message queueing during streaming responses
9798
9838
  - OAuth integration for Gmail and Google Calendar access
9799
- - HTML export with syntax highlighting and collapsible sections
9839
+ - HTML export with syntax highlighting and collapsible sections
@@ -262,6 +262,11 @@ export interface CanonicalModelQueryOptions {
262
262
  availableOnly?: boolean;
263
263
  candidates?: readonly Model<Api>[];
264
264
  }
265
+ /** A canonical record (with query-filtered variants) plus the variant model selected for it. */
266
+ export interface CanonicalModelSelection {
267
+ record: CanonicalModelRecord;
268
+ model: Model<Api>;
269
+ }
265
270
  /**
266
271
  * Model registry - loads and manages models, resolves API keys via AuthStorage.
267
272
  */
@@ -306,6 +311,14 @@ export declare class ModelRegistry {
306
311
  */
307
312
  getAll(): Model<Api>[];
308
313
  getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[];
314
+ /**
315
+ * One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
316
+ * record. The per-query state (candidate-selector set, availability memo,
317
+ * provider rank, candidate order) is built once, so the whole catalog
318
+ * resolves in O(records + candidates) instead of O(records × candidates).
319
+ * This is the path the model selector hydrates from synchronously on open.
320
+ */
321
+ getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[];
309
322
  getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
310
323
  resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
311
324
  getCanonicalId(model: Model<Api>): string | undefined;
@@ -716,15 +716,6 @@ export declare const SETTINGS_SCHEMA: {
716
716
  readonly description: "Show terminal cursor for IME support";
717
717
  };
718
718
  };
719
- readonly clearOnShrink: {
720
- readonly type: "boolean";
721
- readonly default: false;
722
- readonly ui: {
723
- readonly tab: "appearance";
724
- readonly label: "Clear on Shrink";
725
- readonly description: "Clear empty rows when content shrinks (may cause flicker)";
726
- };
727
- };
728
719
  readonly defaultThinkingLevel: {
729
720
  readonly type: "enum";
730
721
  readonly values: readonly [...import("@oh-my-pi/pi-ai").Effort[], "auto"];
@@ -18,7 +18,6 @@ export interface TerminalStateInfo {
18
18
  hyperlinks: boolean;
19
19
  deccara: boolean;
20
20
  screenToScrollback: boolean;
21
- eagerEraseScrollbackRisk: boolean;
22
21
  synchronizedOutput: boolean;
23
22
  multiplexer: string | null;
24
23
  env: {
@@ -1,8 +1,10 @@
1
1
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
2
  import type { HookUIContext } from "../../extensibility/hooks/types";
3
3
  import type { LoadedCustomTool, ToolLoadError } from "./types";
4
- /** Tool path with optional source metadata */
5
- interface ToolPathWithSource {
4
+ /** Tool path with optional source metadata, suitable for forwarding from a
5
+ * parent session to a subagent so the subagent can re-bind tools to its own
6
+ * `CustomToolAPI` without redoing the filesystem scan. */
7
+ export interface ToolPathWithSource {
6
8
  path: string;
7
9
  source?: {
8
10
  provider: string;
@@ -46,12 +48,30 @@ export declare function loadCustomTools(pathsWithSources: ToolPathWithSource[],
46
48
  errors: ToolLoadError[];
47
49
  setUIContext: (uiContext: HookUIContext, hasUI: boolean) => void;
48
50
  }>;
51
+ /**
52
+ * Collect the absolute tool-source paths to load, without importing or
53
+ * binding factories. Hot path on session startup — the scan walks
54
+ * `.omp/tools/`, `.claude/tools/`, the plugin tree, and any configured paths.
55
+ *
56
+ * Subagents reuse the parent's collected paths via the SDK's
57
+ * `preloadedCustomToolPaths` option, then call `loadCustomTools` themselves
58
+ * so each session re-binds factories with its own session-scoped
59
+ * `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
60
+ *
61
+ * @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
62
+ * @param cwd - Current working directory
63
+ */
64
+ export declare function discoverCustomToolPaths(configuredPaths: string[], cwd: string): Promise<ToolPathWithSource[]>;
49
65
  /**
50
66
  * Discover and load tools from standard locations via capability system:
51
67
  * 1. User and project tools discovered by capability providers
52
68
  * 2. Installed plugins (~/.omp/plugins/node_modules/*)
53
69
  * 3. Explicitly configured paths from settings or CLI
54
70
  *
71
+ * Composed of {@link discoverCustomToolPaths} (FS scan) + {@link loadCustomTools}
72
+ * (per-session binding). Subagents skip the first step and just call
73
+ * `loadCustomTools` against the parent's collected paths.
74
+ *
55
75
  * @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
56
76
  * @param cwd - Current working directory
57
77
  * @param builtInToolNames - Names of built-in tools to check for conflicts
@@ -66,4 +86,3 @@ export declare function discoverAndLoadCustomTools(configuredPaths: string[], cw
66
86
  errors: ToolLoadError[];
67
87
  setUIContext: (uiContext: HookUIContext, hasUI: boolean) => void;
68
88
  }>;
69
- export {};
@@ -2,7 +2,7 @@
2
2
  * Extension system for lifecycle events and custom tools.
3
3
  */
4
4
  export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource } from "../slash-commands";
5
- export { discoverAndLoadExtensions, ExtensionRuntimeNotInitializedError, loadExtensionFromFactory, loadExtensions, } from "./loader";
5
+ export { discoverAndLoadExtensions, discoverExtensionPaths, ExtensionRuntimeNotInitializedError, loadExtensionFromFactory, loadExtensions, } from "./loader";
6
6
  export * from "./runner";
7
7
  export * from "./types";
8
8
  export * from "./wrapper";
@@ -38,6 +38,22 @@ export declare function loadExtensionFromFactory(factory: ExtensionFactory, cwd:
38
38
  */
39
39
  export declare function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult>;
40
40
  /**
41
- * Discover and load extensions from standard locations.
41
+ * Discover absolute paths of extensions to load, without importing or
42
+ * binding factories. Hot path on session startup — the scan walks native
43
+ * `.omp`/`.pi` extension capabilities, the installed-plugin tree, and any
44
+ * configured paths.
45
+ *
46
+ * Subagents reuse the parent's collected paths via the SDK's
47
+ * `preloadedExtensionPaths` option, then call {@link loadExtensions} themselves
48
+ * so each session rebuilds Extension instances bound to its OWN
49
+ * `ExtensionAPI` (cwd, eventBus, runtime). Forwarding the parent's
50
+ * `LoadExtensionsResult` directly would reuse handlers/tools/commands that
51
+ * closed over the parent's `cwd` and event bus.
52
+ */
53
+ export declare function discoverExtensionPaths(configuredPaths: string[], cwd: string, disabledExtensionIds?: string[]): Promise<string[]>;
54
+ /**
55
+ * Discover and load extensions from standard locations. Composed of
56
+ * {@link discoverExtensionPaths} (FS scan) + {@link loadExtensions}
57
+ * (per-session binding).
42
58
  */
43
59
  export declare function discoverAndLoadExtensions(configuredPaths: string[], cwd: string, eventBus?: EventBus, disabledExtensionIds?: string[]): Promise<LoadExtensionsResult>;
@@ -13,6 +13,14 @@ import * as path from "node:path";
13
13
  * Exported for tests; production callers use `BUNFS_PACKAGE_ROOT` below.
14
14
  */
15
15
  export declare function __computeBunfsPackageRoot(metaDir: string, pathImpl?: typeof path): string;
16
+ /**
17
+ * Drop overrides whose targets are missing on disk so they can fall through to
18
+ * the canonical-resolution path. Exported for the test seam in #2168.
19
+ *
20
+ * `pathExistsSync` defaults to `fs.existsSync`; the tests inject a stub to
21
+ * simulate the missing-entrypoint failure mode without touching the real FS.
22
+ */
23
+ export declare function __validateLegacyPiPackageRootOverrides(candidates: Record<string, string>, pathExistsSync?: (p: string) => boolean): Record<string, string>;
16
24
  /**
17
25
  * Load a legacy Pi extension module from its real on-disk location.
18
26
  *
@@ -5,6 +5,18 @@
5
5
  * Messages are newline-delimited JSON.
6
6
  */
7
7
  import type { MCPRequestOptions, MCPStdioServerConfig, MCPTransport } from "../../mcp/types";
8
+ /** Subprocess argv for launching an MCP stdio server. */
9
+ export interface StdioSpawnCommand {
10
+ cmd: string[];
11
+ }
12
+ /** Inputs used to resolve platform-specific stdio spawn behavior. */
13
+ export interface ResolveStdioSpawnOptions {
14
+ cwd: string;
15
+ env: Record<string, string | undefined>;
16
+ platform?: NodeJS.Platform;
17
+ }
18
+ /** Resolve the subprocess argv used to launch an MCP stdio server. */
19
+ export declare function resolveStdioSpawnCommand(config: MCPStdioServerConfig, options: ResolveStdioSpawnOptions): Promise<StdioSpawnCommand>;
8
20
  /** Minimal write surface of `Subprocess.stdin` we need for framed sends. */
9
21
  interface FrameSink {
10
22
  write(chunk: string): unknown;
@@ -1,6 +1,7 @@
1
1
  import { Editor, type KeyId } from "@oh-my-pi/pi-tui";
2
2
  import type { AppKeybinding } from "../../config/keybindings";
3
3
  type ConfigurableEditorAction = Extract<AppKeybinding, "app.interrupt" | "app.clear" | "app.exit" | "app.suspend" | "app.display.reset" | "app.thinking.cycle" | "app.model.cycleForward" | "app.model.cycleBackward" | "app.model.select" | "app.model.selectTemporary" | "app.tools.expand" | "app.thinking.toggle" | "app.editor.external" | "app.history.search" | "app.message.dequeue" | "app.clipboard.pasteImage" | "app.clipboard.pasteTextRaw" | "app.clipboard.copyPrompt">;
4
+ export declare function extractBracketedImagePastePaths(data: string): string[] | undefined;
4
5
  export declare function extractBracketedImagePastePath(data: string): string | undefined;
5
6
  /**
6
7
  * Custom editor that handles configurable app-level shortcuts for coding-agent.
@@ -33,8 +34,8 @@ export declare class CustomEditor extends Editor {
33
34
  onCopyPrompt?: () => void;
34
35
  /** Called when the configured image-paste shortcut is pressed. */
35
36
  onPasteImage?: () => Promise<boolean>;
36
- /** Called when a bracketed paste contains exactly one image-file path. */
37
- onPasteImagePath?: (path: string) => void;
37
+ /** Called when a bracketed paste contains one or more image-file paths. */
38
+ onPasteImagePath?: (path: string) => void | Promise<void>;
38
39
  /** Called when the configured raw text-paste shortcut is pressed. */
39
40
  onPasteTextRaw?: () => void;
40
41
  /** Called when the configured dequeue shortcut is pressed. */
@@ -1,25 +1,18 @@
1
1
  import { Container, type NativeScrollbackLiveRegion } from "@oh-my-pi/pi-tui";
2
2
  /**
3
- * Transcript container that freezes the rendered output of every block except
4
- * the bottom-most (live) one on terminals where committed native scrollback is
5
- * immutable.
3
+ * Transcript container that always renders every block's current content and
4
+ * reports the live-region seam (`NativeScrollbackLiveRegion`) that gates the
5
+ * engine's append-only scrollback commits.
6
6
  *
7
- * On ED3-risk terminals with an unobservable viewport (ghostty/kitty/iTerm2/…)
8
- * the renderer cannot clear saved lines (`\x1b[3J` may yank a reader) or query
9
- * whether the user has scrolled, so any block that re-lays-out *after* it has
10
- * scrolled past the viewport leaves a stale duplicate above the live region
11
- * (a finalized assistant message re-wrapping, a tool preview collapsing to its
12
- * compact result, a late async tool completion). The renderer's only safe move
13
- * for such an offscreen edit is to not repaint which is correct only if the
14
- * committed region never changes underneath it.
15
- *
16
- * This container provides that guarantee: a block's render is snapshotted while
17
- * it is the live (bottom-most) block, and once a newer block is appended it
18
- * replays the snapshot instead of recomputing. Mutations after a block leaves
19
- * live are intentionally deferred until the next checkpoint {@link thaw} (prompt
20
- * submit → native-scrollback rebuild), where the whole transcript is replayed
21
- * and any drift reconciles safely. On terminals that can rebuild history this
22
- * freezing is unnecessary, so it renders every block live for full fidelity.
7
+ * The engine never rewrites committed history: rows above the seam that have
8
+ * entered the tape keep whatever bytes they were committed with ("let the
9
+ * history be"), while the visible window always repaints from each block's
10
+ * latest render a late tool result, a post-finalize error pin, or an expand
11
+ * toggle is always reflected on screen. Blocks that are still mutating (an
12
+ * unfinalized tool, a streaming assistant message) stay below the seam so
13
+ * their rows do not enter history while they can still change; a streaming
14
+ * block whose render grows append-only deepens the seam through its settled
15
+ * head so a long reply's scrolled-off rows still reach scrollback mid-stream.
23
16
  */
24
17
  export declare class TranscriptContainer extends Container implements NativeScrollbackLiveRegion {
25
18
  #private;
@@ -27,13 +20,6 @@ export declare class TranscriptContainer extends Container implements NativeScro
27
20
  clear(): void;
28
21
  getNativeScrollbackLiveRegionStart(): number | undefined;
29
22
  getNativeScrollbackCommitSafeEnd(): number | undefined;
30
- /**
31
- * Retire all frozen snapshots so the next render reflects each block's current
32
- * state. Call at reconciliation checkpoints (prompt submit) where the whole
33
- * transcript is replayed into native scrollback and any drift a frozen block
34
- * accumulated is reconciled.
35
- */
36
- thaw(): void;
37
23
  render(width: number): string[];
38
24
  }
39
25
  /**
@@ -6,6 +6,7 @@ import { type PromptTemplate } from "./config/prompt-templates";
6
6
  import { Settings, type SkillsSettings } from "./config/settings";
7
7
  import "./discovery";
8
8
  import { type CustomCommandsLoadResult } from "./extensibility/custom-commands";
9
+ import { type ToolPathWithSource } from "./extensibility/custom-tools";
9
10
  import type { CustomTool } from "./extensibility/custom-tools/types";
10
11
  import { type ExtensionFactory, type ExtensionUIContext, type LoadExtensionsResult, type ToolDefinition } from "./extensibility/extensions";
11
12
  import { type Skill, type SkillWarning } from "./extensibility/skills";
@@ -62,10 +63,41 @@ export interface CreateAgentSessionOptions {
62
63
  /** Disable extension discovery (explicit paths still load). */
63
64
  disableExtensionDiscovery?: boolean;
64
65
  /**
65
- * Pre-loaded extensions (skips file discovery).
66
- * @internal Used by CLI when extensions are loaded early to parse custom flags.
66
+ * Pre-loaded extensions (skips file discovery and the per-session factory
67
+ * call). Used by the CLI when extensions are loaded early to parse custom
68
+ * flags — the same process owns the returned instances, so reusing them is
69
+ * safe.
70
+ *
71
+ * NEVER pass this across session boundaries (e.g. parent → subagent).
72
+ * `Extension` instances close over a parent-bound `ExtensionAPI` (cwd,
73
+ * eventBus, runtime), and reusing them would route tools/handlers/commands
74
+ * back through the parent. For subagents, forward
75
+ * {@link preloadedExtensionPaths} instead.
76
+ *
77
+ * @internal
67
78
  */
68
79
  preloadedExtensions?: LoadExtensionsResult;
80
+ /**
81
+ * Pre-discovered extension source paths. When provided, the filesystem-scan
82
+ * inside `discoverExtensionPaths()` is skipped — the session still calls
83
+ * `loadExtensions()` itself so each `Extension` is bound to THIS session's
84
+ * `ExtensionAPI` (cwd, eventBus, runtime).
85
+ *
86
+ * This is the safe pass-through for parent → subagent forwarding.
87
+ */
88
+ preloadedExtensionPaths?: string[];
89
+ /**
90
+ * Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
91
+ * plugins, etc. When provided, the filesystem-scan inside
92
+ * `discoverCustomToolPaths()` is skipped — subagents inherit the parent's
93
+ * scan result and call `loadCustomTools()` themselves so each session binds
94
+ * tools to its OWN `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
95
+ *
96
+ * Forwarding the loaded `LoadedCustomTool[]` instances directly would reuse
97
+ * the parent's session-bound API and route tool execution back through the
98
+ * parent — wrong for isolated tasks and for pending-action routing.
99
+ */
100
+ preloadedCustomToolPaths?: ToolPathWithSource[];
69
101
  /** Shared event bus for tool/extension communication. Default: creates new bus. */
70
102
  eventBus?: EventBus;
71
103
  /** Skills. Default: discovered from multiple locations */
@@ -178,6 +210,14 @@ export declare function discoverAuthStorage(agentDir?: string): Promise<AuthStor
178
210
  * Discover extensions from cwd.
179
211
  */
180
212
  export declare function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult>;
213
+ /**
214
+ * Path-only counterpart of {@link loadSessionExtensions}: the FS-heavy scan
215
+ * without the per-session module load. Subagents reuse the parent's path list
216
+ * (cached on {@link ToolSession.extensionPaths}) and rebuild Extension
217
+ * instances themselves so each session's `ExtensionAPI` (cwd, eventBus,
218
+ * runtime) is its own.
219
+ */
220
+ export declare function discoverSessionExtensionPaths(options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">, cwd: string, settings: Settings): Promise<string[]>;
181
221
  /**
182
222
  * Load the discovered/configured extensions for a session — everything {@link
183
223
  * createAgentSession} would load except the inline factory extensions it appends
@@ -7,8 +7,7 @@ export interface DiscoveryResult {
7
7
  /**
8
8
  * Discover agents from filesystem and merge with bundled agents.
9
9
  *
10
- * Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
11
- *
10
+ * Precedence (highest wins): project .omp, user .omp, Claude plugin agents, then bundled
12
11
  * @param cwd - Current working directory for project agent discovery
13
12
  */
14
13
  export declare function discoverAgents(cwd: string, home?: string): Promise<DiscoveryResult>;
@@ -4,10 +4,12 @@
4
4
  * Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
5
5
  */
6
6
  import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
7
+ import type { Rule } from "../capability/rule";
7
8
  import { ModelRegistry } from "../config/model-registry";
8
9
  import type { PromptTemplate } from "../config/prompt-templates";
9
10
  import { Settings } from "../config/settings";
10
11
  import { type SettingPath } from "../config/settings-schema";
12
+ import type { ToolPathWithSource } from "../extensibility/custom-tools";
11
13
  import type { CustomTool } from "../extensibility/custom-tools/types";
12
14
  import { type Skill } from "../extensibility/skills";
13
15
  import type { HindsightSessionState } from "../hindsight/state";
@@ -70,6 +72,20 @@ export interface ExecutorOptions {
70
72
  skills?: Skill[];
71
73
  promptTemplates?: PromptTemplate[];
72
74
  workspaceTree?: WorkspaceTree;
75
+ /** Parent-discovered rules, forwarded to skip rule discovery in the subagent. */
76
+ rules?: Rule[];
77
+ /**
78
+ * Parent's discovered extension source paths. Forwarded to skip the
79
+ * extension FS scan in the subagent; the subagent then re-binds each
80
+ * extension against its own `ExtensionAPI` (cwd, eventBus, runtime).
81
+ */
82
+ preloadedExtensionPaths?: string[];
83
+ /**
84
+ * Parent's discovered custom-tool source paths. Forwarded to skip the
85
+ * `.omp/tools/` FS scan in the subagent; the subagent then re-binds each
86
+ * tool against its own `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
87
+ */
88
+ preloadedCustomToolPaths?: ToolPathWithSource[];
73
89
  mcpManager?: MCPManager;
74
90
  authStorage?: AuthStorage;
75
91
  modelRegistry?: ModelRegistry;
@@ -32,7 +32,7 @@ export declare const TINY_WORKER_ARG = "--tiny-worker";
32
32
  */
33
33
  export declare function tinyWorkerEnvOverlay(env: Record<string, string | undefined>, deviceSetting: string | undefined, dtypeSetting: string | undefined): Record<string, string>;
34
34
  interface SpawnedSubprocess {
35
- proc: Subprocess<"ignore", "inherit", "inherit">;
35
+ proc: Subprocess<"ignore", "ignore", "ignore">;
36
36
  inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
37
37
  errors: Set<(error: Error) => void>;
38
38
  /**
@@ -2,8 +2,10 @@ import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
2
2
  import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
3
3
  import type { FetchImpl, ToolChoice } from "@oh-my-pi/pi-ai";
4
4
  import type { AsyncJobManager } from "../async/job-manager";
5
+ import type { Rule } from "../capability/rule";
5
6
  import type { PromptTemplate } from "../config/prompt-templates";
6
7
  import type { Settings } from "../config/settings";
8
+ import type { ToolPathWithSource } from "../extensibility/custom-tools";
7
9
  import type { Skill } from "../extensibility/skills";
8
10
  import type { GoalModeState, GoalRuntime } from "../goals";
9
11
  import type { HindsightSessionState } from "../hindsight/state";
@@ -107,6 +109,21 @@ export interface ToolSession {
107
109
  skills?: Skill[];
108
110
  /** Pre-loaded prompt templates */
109
111
  promptTemplates?: PromptTemplate[];
112
+ /** Pre-loaded rules (forwarded to subagents to skip re-discovery). */
113
+ rules?: Rule[];
114
+ /**
115
+ * Pre-discovered extension source paths. Forwarded to subagents so they
116
+ * skip the FS scan but still re-bind extensions to their own session-scoped
117
+ * `ExtensionAPI` (cwd, eventBus, runtime). Inline extension factories
118
+ * (`<inline-N>`) are NOT included — those are session-local.
119
+ */
120
+ extensionPaths?: string[];
121
+ /**
122
+ * Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
123
+ * plugins, etc. Forwarded to subagents so they skip the FS scan but still
124
+ * re-bind tools to their own session-scoped `CustomToolAPI`.
125
+ */
126
+ customToolPaths?: ToolPathWithSource[];
110
127
  /** Whether LSP integrations are enabled */
111
128
  enableLsp?: boolean;
112
129
  /** Whether an edit-capable tool is available in this session (controls hashline output) */
@@ -40,6 +40,7 @@ declare const todoSchema: z.ZodObject<{
40
40
  note: "note";
41
41
  rm: "rm";
42
42
  start: "start";
43
+ view: "view";
43
44
  }>;
44
45
  list: z.ZodOptional<z.ZodArray<z.ZodObject<{
45
46
  phase: z.ZodString;
@@ -113,6 +114,7 @@ export declare class TodoTool implements AgentTool<typeof todoSchema, TodoToolDe
113
114
  note: "note";
114
115
  rm: "rm";
115
116
  start: "start";
117
+ view: "view";
116
118
  }>;
117
119
  list: z.ZodOptional<z.ZodArray<z.ZodObject<{
118
120
  phase: z.ZodString;
@@ -19,6 +19,14 @@ export declare function uriHyperlink(uri: string, displayText: string): string;
19
19
  * `www.example.com` inputs are linked as `https://www.example.com`.
20
20
  */
21
21
  export declare function urlHyperlink(url: string, displayText: string): string;
22
+ /**
23
+ * Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL,
24
+ * bypassing terminal capability auto-detection. Used for auth prompts where
25
+ * an inert "click" label blocks login on terminals whose capabilities are
26
+ * not advertised. Still returns plain text when the user has explicitly
27
+ * opted out via `tui.hyperlinks=off`.
28
+ */
29
+ export declare function urlHyperlinkAlways(url: string, displayText: string): string;
22
30
  /**
23
31
  * Wrap `displayText` in an OSC 8 hyperlink pointing at a filesystem path.
24
32
  *
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": "15.10.8",
4
+ "version": "15.10.10",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,14 +47,14 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.10.8",
51
- "@oh-my-pi/omp-stats": "15.10.8",
52
- "@oh-my-pi/pi-agent-core": "15.10.8",
53
- "@oh-my-pi/pi-ai": "15.10.8",
54
- "@oh-my-pi/pi-mnemopi": "15.10.8",
55
- "@oh-my-pi/pi-natives": "15.10.8",
56
- "@oh-my-pi/pi-tui": "15.10.8",
57
- "@oh-my-pi/pi-utils": "15.10.8",
50
+ "@oh-my-pi/hashline": "15.10.10",
51
+ "@oh-my-pi/omp-stats": "15.10.10",
52
+ "@oh-my-pi/pi-agent-core": "15.10.10",
53
+ "@oh-my-pi/pi-ai": "15.10.10",
54
+ "@oh-my-pi/pi-mnemopi": "15.10.10",
55
+ "@oh-my-pi/pi-natives": "15.10.10",
56
+ "@oh-my-pi/pi-tui": "15.10.10",
57
+ "@oh-my-pi/pi-utils": "15.10.10",
58
58
  "@opentelemetry/api": "^1.9.1",
59
59
  "@opentelemetry/context-async-hooks": "^2.7.1",
60
60
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -64,22 +64,16 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
64
64
  }
65
65
 
66
66
  const filteredCanonical = modelRegistry
67
- .getCanonicalModels({ availableOnly: true, candidates: filteredModels })
68
- .map(record => {
69
- const selected = modelRegistry.resolveCanonicalModel(record.id, {
70
- availableOnly: true,
71
- candidates: filteredModels,
72
- });
73
- if (!selected) return undefined;
74
- return {
67
+ .getCanonicalModelSelections({ availableOnly: true, candidates: filteredModels })
68
+ .map(
69
+ ({ record, model: selected }): CanonicalRow => ({
75
70
  canonical: record.id,
76
71
  selected: `${selected.provider}/${selected.id}`,
77
72
  variants: String(record.variants.length),
78
73
  context: formatNumber(selected.contextWindow),
79
74
  maxOut: formatNumber(selected.maxTokens),
80
- } satisfies CanonicalRow;
81
- })
82
- .filter((row): row is CanonicalRow => row !== undefined)
75
+ }),
76
+ )
83
77
  .sort((left, right) => left.canonical.localeCompare(right.canonical));
84
78
 
85
79
  if (filteredModels.length === 0 && filteredCanonical.length === 0) {