@oh-my-pi/pi-coding-agent 15.9.1 → 15.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +68 -2
  2. package/dist/types/cli/classify-install-target.d.ts +5 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +104 -0
  4. package/dist/types/commands/dry-balance.d.ts +31 -0
  5. package/dist/types/config/model-registry.d.ts +2 -0
  6. package/dist/types/config/models-config-schema.d.ts +3 -0
  7. package/dist/types/config/settings-schema.d.ts +13 -4
  8. package/dist/types/config/settings.d.ts +11 -0
  9. package/dist/types/discovery/helpers.d.ts +1 -0
  10. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
  11. package/dist/types/hindsight/bank.d.ts +17 -9
  12. package/dist/types/hindsight/mental-models.d.ts +1 -1
  13. package/dist/types/hindsight/state.d.ts +9 -3
  14. package/dist/types/mcp/manager.d.ts +1 -1
  15. package/dist/types/modes/components/assistant-message.d.ts +11 -0
  16. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  17. package/dist/types/modes/components/error-banner.d.ts +11 -0
  18. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  19. package/dist/types/modes/components/transcript-container.d.ts +4 -2
  20. package/dist/types/modes/components/user-message.d.ts +1 -1
  21. package/dist/types/modes/image-references.d.ts +17 -0
  22. package/dist/types/modes/interactive-mode.d.ts +7 -0
  23. package/dist/types/modes/types.d.ts +7 -0
  24. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  25. package/dist/types/session/agent-session.d.ts +9 -0
  26. package/dist/types/session/auth-storage.d.ts +2 -2
  27. package/dist/types/session/blob-store.d.ts +12 -11
  28. package/dist/types/session/session-manager.d.ts +5 -3
  29. package/dist/types/system-prompt.d.ts +2 -0
  30. package/dist/types/task/types.d.ts +2 -0
  31. package/dist/types/tiny/title-client.d.ts +16 -1
  32. package/dist/types/tool-discovery/mode.d.ts +8 -0
  33. package/dist/types/tools/archive-reader.d.ts +5 -1
  34. package/dist/types/tools/index.d.ts +16 -0
  35. package/dist/types/tools/path-utils.d.ts +11 -0
  36. package/dist/types/tui/hyperlink.d.ts +12 -0
  37. package/dist/types/web/search/render.d.ts +1 -2
  38. package/package.json +9 -9
  39. package/src/cli/classify-install-target.ts +31 -5
  40. package/src/cli/dry-balance-cli.ts +823 -0
  41. package/src/cli/plugin-cli.ts +45 -0
  42. package/src/cli/web-search-cli.ts +0 -1
  43. package/src/cli-commands.ts +1 -0
  44. package/src/commands/dry-balance.ts +43 -0
  45. package/src/config/model-registry.ts +60 -4
  46. package/src/config/models-config-schema.ts +2 -0
  47. package/src/config/settings-schema.ts +14 -4
  48. package/src/config/settings.ts +38 -0
  49. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
  50. package/src/discovery/github.ts +37 -1
  51. package/src/discovery/helpers.ts +3 -1
  52. package/src/eval/__tests__/agent-bridge.test.ts +72 -0
  53. package/src/eval/py/tool-bridge.ts +43 -5
  54. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +31 -2
  55. package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
  56. package/src/hindsight/backend.ts +184 -35
  57. package/src/hindsight/bank.ts +32 -22
  58. package/src/hindsight/mental-models.ts +1 -1
  59. package/src/hindsight/state.ts +21 -7
  60. package/src/internal-urls/docs-index.generated.ts +6 -6
  61. package/src/internal-urls/omp-protocol.ts +8 -2
  62. package/src/main.ts +7 -1
  63. package/src/mcp/manager.ts +40 -21
  64. package/src/modes/components/assistant-message.ts +22 -0
  65. package/src/modes/components/custom-editor.ts +14 -2
  66. package/src/modes/components/error-banner.ts +33 -0
  67. package/src/modes/components/tool-execution.ts +44 -0
  68. package/src/modes/components/transcript-container.ts +102 -30
  69. package/src/modes/components/tree-selector.ts +29 -2
  70. package/src/modes/components/user-message.ts +9 -2
  71. package/src/modes/controllers/event-controller.ts +42 -3
  72. package/src/modes/controllers/input-controller.ts +41 -3
  73. package/src/modes/image-references.ts +111 -0
  74. package/src/modes/interactive-mode.ts +48 -13
  75. package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
  76. package/src/modes/types.ts +10 -1
  77. package/src/modes/utils/ui-helpers.ts +23 -2
  78. package/src/prompts/agents/explore.md +1 -0
  79. package/src/prompts/agents/librarian.md +1 -0
  80. package/src/prompts/ci-green-request.md +5 -3
  81. package/src/prompts/dry-balance-bench.md +8 -0
  82. package/src/prompts/system/project-prompt.md +1 -0
  83. package/src/sdk.ts +99 -18
  84. package/src/session/agent-session.ts +103 -19
  85. package/src/session/auth-storage.ts +4 -0
  86. package/src/session/blob-store.ts +96 -9
  87. package/src/session/session-manager.ts +19 -10
  88. package/src/system-prompt.ts +4 -0
  89. package/src/task/executor.ts +6 -2
  90. package/src/task/index.ts +8 -7
  91. package/src/task/types.ts +2 -0
  92. package/src/tiny/title-client.ts +7 -1
  93. package/src/tool-discovery/mode.ts +24 -0
  94. package/src/tools/archive-reader.ts +339 -31
  95. package/src/tools/bash.ts +3 -4
  96. package/src/tools/fetch.ts +29 -9
  97. package/src/tools/gh.ts +65 -11
  98. package/src/tools/index.ts +22 -8
  99. package/src/tools/job.ts +3 -3
  100. package/src/tools/memory-reflect.ts +2 -2
  101. package/src/tools/path-utils.ts +21 -0
  102. package/src/tools/read.ts +58 -12
  103. package/src/tools/search-tool-bm25.ts +4 -6
  104. package/src/tools/search.ts +78 -12
  105. package/src/tui/hyperlink.ts +42 -7
  106. package/src/utils/file-mentions.ts +7 -107
  107. package/src/utils/title-generator.ts +58 -37
  108. package/src/web/search/index.ts +2 -2
  109. package/src/web/search/render.ts +20 -52
package/CHANGELOG.md CHANGED
@@ -2,6 +2,73 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.9.5] - 2026-06-05
6
+ ### Added
7
+
8
+ - Added a persistent error banner pinned above the editor when an assistant turn ends on a provider error (e.g. Anthropic's "Output blocked by content filtering policy"). The transcript `Error: …` line scrolls away as the conversation grows, so terminal turns that ended on a stream error could pass unnoticed; the banner stays in the fixed region above the input and is cleared when the next turn starts.
9
+
10
+ - Added bold, underlined, clickable `[Image #N]` placeholders in the draft editor and sent user-message bubbles, backed by extension-bearing blob-store sidecar files so terminal `file://` links open in image viewers.
11
+ - Added the active model identifier (`provider/id`) to the system prompt's `<workstation>` block so the agent knows which model it is running as. Gated by the new `includeModelInPrompt` setting (default on); the base prompt is rebuilt on a mid-session model switch so the surfaced identifier stays current.
12
+ - Added `OLLAMA_HOST` support for implicit local Ollama discovery when `OLLAMA_BASE_URL` is unset, so OMP picks up the same host setting used by Ollama.
13
+ - Added `OLLAMA_CONTEXT_LENGTH` as a positive-integer context-window override for implicit local Ollama discovery, so users can correct OMP context budgeting without writing per-model overrides.
14
+
15
+ ### Changed
16
+
17
+ - Changed `tools.discoveryMode` to default to `auto`, which keeps discovery off for small tool sets and automatically switches to MCP-only tool discovery when more than 40 tools are registered.
18
+
19
+ ### Fixed
20
+
21
+ - Fixed user-message rendering to materialize image links from embedded image blocks when rebuilding chat output, so image placeholders remain clickable after replayed or restored messages
22
+ - Fixed queued/steering user messages carrying a pasted image rendering out of order — sometimes dropping the user bubble *below* the very tool output it was sent to steer. `EventController.#handleMessageStart` awaited async image-link materialization between the user `message_start` and `addMessageToChat`; since `AgentSession.#emit` dispatches TUI listeners fire-and-forget, that mid-handler yield let the next synchronously-handled events (assistant `message_start`, tool execution start/end) append their components first, scrambling transcript order and live-region block boundaries. The bubble is now appended synchronously, with clickable image links still materialized via the synchronous blob-store fallback.
23
+ - Fixed tool execution cards to finalize promptly when a turn is abandoned or completed so stale streaming previews and frozen spinner frames no longer keep transcript rows in the live region
24
+ - Fixed `read` and `search` TUI rendering to emit OSC 8 hyperlinks for HTTP URLs, `local://` resources backed by files, and filesystem search targets, including line-specific links for search match rows.
25
+ - Fixed aborted streaming assistant messages staying frozen before their red "Operation aborted" label when status rows were appended underneath on ED3-risk terminals.
26
+ - Fixed `omp` / `omp -c` stacking a fresh welcome screen and transcript on top of the previous run's leftover terminal scrollback. The cold-launch transcript render was the only session-load path that did not pass `clearTerminalHistory`, so the TUI's scrollback-preserving initial paint left the prior run's welcome + conversation above the new one; the cold launch now clears native scrollback before painting, matching every in-process session switch.
27
+ - Fixed a long streamed assistant reply dropping its earlier lines on ED3-risk terminals (Ghostty/kitty/iTerm2) once it grew past the viewport — the head scrolled off the top and never reached scrollback, so the reply rendered as a ~viewport-tall circular buffer of only its latest lines. `AssistantMessageComponent` now reports itself as an append-only transcript block and `TranscriptContainer` surfaces the resulting commit-safe boundary, so the renderer commits the scrolled-off head to native scrollback instead of discarding it (volatile tool previews stay deferred as before).
28
+
29
+ ### Security
30
+
31
+ - Blocked OSC 8 hyperlink wrapping for URI targets containing terminal control bytes to avoid rendering malformed control-sequence links
32
+
33
+ ## [15.9.4] - 2026-06-05
34
+ ### Fixed
35
+
36
+ - Fixed chat transcript updates after submitting input so frozen scrollback is only thawed when native scrollback replay succeeds, preventing misplaced or duplicated rows when the viewport is not at the tail
37
+ - Fixed `read` of `.zip` archives to list the central directory without inflating every member, so large or corrupt zip payloads no longer freeze directory reads; member contents are inflated only when a specific entry is read.
38
+ - Fixed the Python `eval` kernel being hard-killed (and its persistent session state lost) when a cell blocked in `parallel()` / `agent()` was interrupted. Each `agent()`/`tool.*` call blocks a kernel worker thread in a synchronous `urllib` request to the host bridge, and `parallel()`'s `ThreadPoolExecutor` exit joins those threads — so the kernel cannot unwind a `KeyboardInterrupt` until every in-flight bridge call returns. A wide subagent fan-out's teardown routinely outlasted the kernel's 5s SIGINT-escalation window, so the kernel was force-killed (surfacing `[kernel] Python kernel shutdown`) while the subagents were still winding down. The host bridge now resolves an in-flight call the instant the cell's signal aborts, so the kernel unwinds cleanly and keeps its state; the already-signaled subagent continues tearing down in the background.
39
+ - Fixed `github` tool `run_watch` op ignoring the explicit `repo` argument and silently watching the cwd-inferred repository in nested/umbrella workspaces. `executeRunWatch` passed `undefined` for the user-supplied `repo` to `resolveGitHubRepo`, so a call like `{op: "run_watch", repo: "owner/cxf", branch: "main"}` fell back to `gh repo view` in cwd and streamed `watching <sha> on <cwd-repo>` against the wrong repository. The explicit `repo` now takes precedence over the cwd inference, and the no-`branch`/no-`run` path refuses to derive the watched commit from `git HEAD` unless the cwd actually points at the resolved repo — otherwise it raises a `ToolError` telling the caller to pass `branch` or `run` instead of silently rebinding to an unrelated commit ([#1949](https://github.com/can1357/oh-my-pi/issues/1949)).
40
+ - Fixed `omp plugin install <local-path>` failing with `Invalid package name: .` (and similar) for cwd-relative (`.`, `./pkg`), absolute (`/abs`, `C:\…`, `\\unc`), and tilde-prefixed (`~/pkg`) specs. `classifyInstallTarget` now returns a `local` arm in addition to `marketplace`/`npm`, and `plugin install` routes those specs to `PluginManager.link()` — the same code path as `omp plugin link`. ([#1945](https://github.com/can1357/oh-my-pi/issues/1945))
41
+ - Fixed the `web_search` result renderer capping the synthesized answer at 12 lines even when expanded, while the Sources list expanded in full — so a long answer stayed truncated ("… N more lines") after `Ctrl+O`, making the expand toggle look like a no-op and dwarfing the answer next to its sources. The answer now renders the full text (markdown-formatted) when expanded and a short markdown preview when collapsed, matching the sources' collapse/expand behavior. The answer is also rendered through the Markdown component instead of dimmed raw lines, so headings, bold, lists, and code in the answer display formatted.
42
+
43
+ ## [15.9.3] - 2026-06-05
44
+
45
+ ### Fixed
46
+
47
+ - Fixed `@`-mention auto-read injecting an unrelated, same-named file when a mention did not point at a real path — e.g. an npm scope like `@scope/`, a partial path, or a bare token. `generateFileMentionMessages` resolution previously fell back to prefix and repo-wide fuzzy matching (globbing the whole project on every such mention) and auto-read the single "best" guess. Resolution is now exact-only: a mention is auto-read only when it resolves to an existing file or directory; otherwise it is left as prose. The TUI `@`-selector already inserts the real, complete path before send, so post-send guessing was both unnecessary and the source of the wrong-file reads. Directories still resolve and are listed. Removes the per-mention `**/*` project scan.
48
+
49
+ ## [15.9.2] - 2026-06-05
50
+
51
+ ### Added
52
+
53
+ - Added an encrypted local auth-broker snapshot cache for `discoverAuthStorage`, with `OMP_AUTH_BROKER_SNAPSHOT_TTL_MS` and `OMP_AUTH_BROKER_SNAPSHOT_CACHE`, so fresh cached broker credentials can boot without a blocking `/v1/snapshot` fetch and survive broker-down startup windows.
54
+ - Added `dry-balance` CLI command to perform a dry-run OAuth account balancing check across configurable random session IDs, with sample and concurrency options, JSON output, and success/failure summary reporting
55
+ - Added `--json` output mode and machine-readable result format to `omp dry-balance` for automated use
56
+ - Added `omitMaxOutputTokens` to `models.yml` model definitions and `modelOverrides`, so users can opt a model out of the on-the-wire `max_output_tokens` / `max_tokens` cap while keeping the catalog `maxTokens` for local budgeting. Intended for Ollama-style proxies whose upstream output limit OMP cannot discover. ([#1881](https://github.com/can1357/oh-my-pi/issues/1881))
57
+
58
+ ### Fixed
59
+
60
+ - Fixed TTSR rule-violation injections leaking the absolute home directory to the model: the `ttsr-interrupt` / `ttsr-tool-reminder` blocks rendered the matched rule's `path` as its absolute on-disk path (e.g. `/Users/me/Projects/app/.omp/rules/no-any.md`). The path is now relativized to the session cwd when the rule lives in the project (`.omp/rules/no-any.md`), or `~`-relative when it lives under home, so no absolute path is fed into the agent's context outside the system prompt.
61
+ - Fixed `AsyncJobManager.instance()` being cleared while the owning top-level session was still live, which broke the `task` async path with "Async execution is enabled but no async job manager is available" until process restart. Any in-process secondary top-level `createAgentSession()` call (e.g. the Agent Control Center's create flow in `agent-dashboard.ts`) constructed a fresh `AsyncJobManager`, overwrote the singleton, and then cleared it on its own dispose. Secondary sessions now leave the live singleton untouched, and their dispose-time cleanup is scoped so it can no longer cancel the primary session's running bash/task jobs. `bash` / `task` / `job` tools and session job snapshots now resolve the manager through session-scoped async manager wiring rather than `AsyncJobManager.instance()`, so a secondary in-process top-level session cannot accidentally register background work on the owning session's manager or report the owning session's jobs; subagents still inherit the parent's manager via their scoped async manager. Startup failures after a top-level session installs its manager now clear and dispose that manager before the next session decides whether it can create its own ([#1923](https://github.com/can1357/oh-my-pi/issues/1923)).
62
+ - Fixed the `task` tool returning a hard `Async execution is enabled but no async job manager is available.` error when `async.enabled` was true but `AsyncJobManager.instance()` returned `undefined`, leaving `task` non-functional for the rest of the session. The tool now falls back to the existing synchronous execution path (which still runs subagents concurrently via `mapWithConcurrencyLimit`), and logs a warning so the missing-manager state stays diagnosable ([#1922](https://github.com/can1357/oh-my-pi/issues/1922)).
63
+ - Fixed Hindsight retain/recall/reflect calls staying pinned to the bank that was selected when the session started after the operator edited `hindsight.bankId`, `hindsight.bankIdPrefix`, or `hindsight.scoping` mid-session. The backend now subscribes to those settings via `onHindsightScopeChanged` and rebuilds the active `HindsightSessionState` against the recomputed scope, disposing the old state after flushing its queue so in-flight tool-initiated retains still land in the bank they were enqueued for. Also renamed `ensureBankMission` to `ensureBankExists` so a blank `bankMission` no longer skips bank creation entirely, and called it before mental-model bootstrap so `createMentalModel` is never the first POST against a missing bank. `AgentSession.dispose` now flushes the retain queue before clearing `#hindsightSessionState`, since the queue's identity guard would otherwise drop the spliced batch ([#1902](https://github.com/can1357/oh-my-pi/issues/1902)).
64
+ - Fixed `/tree` rendering a bare "No entries found" line on a fresh session where the only persisted entries are the `model_change` + `thinking_level_change` written by `sdk.ts` at startup — both are hidden by the tree-selector's default filter, so `#filteredNodes.length === 0` while `tree.length === 2` and the controller's `tree.length === 0` short-circuit never fired. The selector now splits the empty-state into three distinct shapes — truly empty tree, search query with no matches, and filter mode rejecting every entry — surfacing the cause and the recovery key (`Alt+A` to show all, `Backspace` to clear a stale search) so users on a fresh session can see immediately that the panel isn't broken ([#1909](https://github.com/can1357/oh-my-pi/issues/1909)).
65
+ - Fixed remote MCP OAuth refresh failures leaving stale credentials in `agent.db`: when the token endpoint returns a definitive failure (`invalid_grant`, `invalid_token`, `revoked`, plain 401/403 not classified as transient), `MCPManager#resolveAuthConfig` now drops the credential via `AuthStorage.remove(credentialId)` and skips re-attaching the dead `Authorization: Bearer …` header. Previously a revoked refresh token kept producing `401 invalid_token` on every MCP request and survived restarts, so users had to hand-clear the credential row to recover; the next connect now surfaces a clean auth error and `/mcp reauth <server>` (or `/mcp unauth`) recovers without restarting. Transient refresh failures (network/`fetch failed`/`ECONNREFUSED`) still fall back to the existing access token ([#1908](https://github.com/can1357/oh-my-pi/issues/1908)).
66
+ - Fixed `omp://docs` and `omp://docs/...` internal documentation URLs in the distributed package to resolve through the embedded documentation index instead of failing with `Documentation file not found` ([#1898](https://github.com/can1357/oh-my-pi/issues/1898)).
67
+ - Fixed the `github` discovery provider silently ignoring `.github/skills/<name>/SKILL.md`, GitHub's documented Agent Skills layout. The provider now registers a `skills` capability (priority 30, project-only) that scans `.github/skills/` non-recursively via `scanSkillsFromDir` with `requireDescription: true`, matching the Agent Skills spec and the sibling `native`/`omp-plugins` providers ([#1906](https://github.com/can1357/oh-my-pi/issues/1906)).
68
+ - Fixed inline images rendering as a wall of empty PUA box glyphs with laggy scrolling on Kitty-protocol terminals that do not honor Unicode placeholders (most notably WezTerm and tmux/screen passthrough to a non-Kitty outer terminal). The 15.9 placeholder rollout enabled the `U=1`/U+10EEEE grid for every Kitty-protocol path; it now defaults on only for `kitty` and `ghostty`, with `PI_NO_KITTY_PLACEHOLDERS=1` as a hard opt-out and `PI_KITTY_PLACEHOLDERS=1` as opt-in for terminals (e.g. wezterm nightlies) that have since added support ([#1877](https://github.com/can1357/oh-my-pi/issues/1877)).
69
+ - Fixed auto session-title generation failures being swallowed without an actionable diagnostic. Title generation now logs structured start, missing-model/API-key, provider-error, empty-result, and exception outcomes with the session id and resolved title model; the interactive auto-title caller also logs uncaught persistence/generation errors instead of dropping them. ([#1892](https://github.com/can1357/oh-my-pi/issues/1892))
70
+ - Fixed `TranscriptContainer` reporting the live block boundary to the TUI again, so ED3-risk foreground streaming can append newly sealed transcript blocks to native scrollback once while deferring only the active live block.
71
+
5
72
  ## [15.9.1] - 2026-06-04
6
73
 
7
74
  ### Added
@@ -20,7 +87,6 @@
20
87
  ### Fixed
21
88
 
22
89
  - Fixed a streamed assistant message freezing at a partial prefix (e.g. only "Nat" of "Natives built, now…") on ED3-risk terminals (Ghostty/kitty/iTerm2/Alacritty), with the final text appearing only after a resize. `TranscriptContainer` freezes each non-live block by replaying its last live render, but render coalescing can finalize a block's content and append the next block within the same throttled frame — so the block was sealed at its stale mid-stream snapshot and never repainted until the next `thaw`. The block that was live on the previous render is now recomputed once on the live→frozen transition, sealing it at its final content.
23
-
24
90
  - Fixed ACP/RPC stdio startup so protocol frames are no longer consumed as one-shot piped prompt input before the JSON-RPC transport starts.
25
91
  - Fixed `omp completions` to await the completion script write before exiting.
26
92
  - Fixed `AssistantMessageComponent` exposing its stable-prefix completion API again so streamed assistant messages remain unstable until explicitly completed.
@@ -9333,4 +9399,4 @@ Initial public release.
9333
9399
  - Git branch display in footer
9334
9400
  - Message queueing during streaming responses
9335
9401
  - OAuth integration for Gmail and Google Calendar access
9336
- - HTML export with syntax highlighting and collapsible sections
9402
+ - HTML export with syntax highlighting and collapsible sections
@@ -1,4 +1,7 @@
1
- export declare function classifyInstallTarget(spec: string, knownMarketplaces: Set<string>): {
1
+ export type ClassifiedInstallTarget = {
2
+ type: "local";
3
+ path: string;
4
+ } | {
2
5
  type: "marketplace";
3
6
  name: string;
4
7
  marketplace: string;
@@ -6,3 +9,4 @@ export declare function classifyInstallTarget(spec: string, knownMarketplaces: S
6
9
  type: "npm";
7
10
  spec: string;
8
11
  };
12
+ export declare function classifyInstallTarget(spec: string, knownMarketplaces: Set<string>): ClassifiedInstallTarget;
@@ -0,0 +1,104 @@
1
+ import type { Api, AssistantMessageEventStream, Context, Model, OAuthAccess, OAuthAccessResolution, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
2
+ import type { CanonicalModelVariant } from "../config/model-equivalence";
3
+ import { type CanonicalModelQueryOptions } from "../config/model-registry";
4
+ import { Settings } from "../config/settings";
5
+ export interface DryBalanceCommandArgs {
6
+ model?: string;
7
+ flags: {
8
+ model?: string;
9
+ count?: number;
10
+ concurrency?: number;
11
+ json?: boolean;
12
+ bench?: boolean;
13
+ };
14
+ }
15
+ export interface DryBalanceAuthOptions {
16
+ baseUrl?: string;
17
+ modelId?: string;
18
+ signal?: AbortSignal;
19
+ }
20
+ export interface DryBalanceAuthStorage {
21
+ getOAuthAccess(provider: string, sessionId?: string, options?: DryBalanceAuthOptions): Promise<OAuthAccess | undefined>;
22
+ getOAuthAccesses?(provider: string, options?: DryBalanceAuthOptions): Promise<OAuthAccessResolution[]>;
23
+ }
24
+ export interface DryBalanceModelRegistry {
25
+ authStorage: DryBalanceAuthStorage;
26
+ getAll(): Model<Api>[];
27
+ getAvailable(): Model<Api>[];
28
+ getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
29
+ getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
30
+ resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
31
+ getCanonicalId?(model: Model<Api>): string | undefined;
32
+ }
33
+ export interface DryBalanceRuntime {
34
+ modelRegistry: DryBalanceModelRegistry;
35
+ settings?: Settings;
36
+ close?: () => void;
37
+ }
38
+ export interface DryBalanceAccountStat {
39
+ account: string;
40
+ count: number;
41
+ percent: number;
42
+ }
43
+ export interface DryBalanceFailureStat {
44
+ reason: string;
45
+ count: number;
46
+ percent: number;
47
+ }
48
+ export interface DryBalanceBenchSuccessResult {
49
+ ok: true;
50
+ account: string;
51
+ ttftMs: number;
52
+ durationMs: number;
53
+ outputTokens: number;
54
+ tokensPerSecond: number;
55
+ }
56
+ export interface DryBalanceBenchFailureResult {
57
+ ok: false;
58
+ account?: string;
59
+ error: string;
60
+ }
61
+ export type DryBalanceBenchResult = DryBalanceBenchSuccessResult | DryBalanceBenchFailureResult;
62
+ export interface DryBalanceBenchSummary {
63
+ total: number;
64
+ success: {
65
+ total: number;
66
+ averageTtftMs: number | null;
67
+ averageTokensPerSecond: number | null;
68
+ };
69
+ failure: {
70
+ total: number;
71
+ reasons: DryBalanceFailureStat[];
72
+ };
73
+ results: DryBalanceBenchResult[];
74
+ }
75
+ export interface DryBalanceSummary {
76
+ model: string;
77
+ provider: string;
78
+ samples: number;
79
+ concurrency: number;
80
+ success: {
81
+ total: number;
82
+ accounts: DryBalanceAccountStat[];
83
+ };
84
+ failure: {
85
+ total: number;
86
+ reasons: DryBalanceFailureStat[];
87
+ };
88
+ bench?: DryBalanceBenchSummary;
89
+ }
90
+ type DryBalanceStreamSimple = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
91
+ export interface DryBalanceDependencies {
92
+ createRuntime?: () => Promise<DryBalanceRuntime>;
93
+ randomSessionId?: () => string;
94
+ writeStdout?: (text: string) => void;
95
+ writeStderr?: (text: string) => void;
96
+ setExitCode?: (code: number) => void;
97
+ streamSimple?: DryBalanceStreamSimple;
98
+ now?: () => number;
99
+ stdoutIsTTY?: boolean;
100
+ stderrIsTTY?: boolean;
101
+ }
102
+ export declare function formatDryBalanceText(summary: DryBalanceSummary): string;
103
+ export declare function runDryBalanceCommand(command: DryBalanceCommandArgs, deps?: DryBalanceDependencies): Promise<DryBalanceSummary>;
104
+ export {};
@@ -0,0 +1,31 @@
1
+ import { Command } from "@oh-my-pi/pi-utils/cli";
2
+ export default class DryBalance extends Command {
3
+ static description: string;
4
+ static args: {
5
+ model: import("@oh-my-pi/pi-utils/cli").ArgDescriptor & {
6
+ description: string;
7
+ required: false;
8
+ };
9
+ };
10
+ static flags: {
11
+ model: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
12
+ description: string;
13
+ };
14
+ count: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
15
+ description: string;
16
+ default: number;
17
+ };
18
+ concurrency: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
19
+ description: string;
20
+ default: number;
21
+ };
22
+ json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
23
+ description: string;
24
+ };
25
+ bench: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
26
+ description: string;
27
+ };
28
+ };
29
+ static examples: string[];
30
+ run(): Promise<void>;
31
+ }
@@ -104,6 +104,7 @@ export declare const ModelsConfigFile: ConfigFile<{
104
104
  premiumMultiplier?: number | undefined;
105
105
  contextWindow?: number | undefined;
106
106
  maxTokens?: number | undefined;
107
+ omitMaxOutputTokens?: boolean | undefined;
107
108
  headers?: Record<string, string> | undefined;
108
109
  compat?: {
109
110
  supportsStore?: boolean | undefined;
@@ -166,6 +167,7 @@ export declare const ModelsConfigFile: ConfigFile<{
166
167
  premiumMultiplier?: number | undefined;
167
168
  contextWindow?: number | undefined;
168
169
  maxTokens?: number | undefined;
170
+ omitMaxOutputTokens?: boolean | undefined;
169
171
  headers?: Record<string, string> | undefined;
170
172
  compat?: {
171
173
  supportsStore?: boolean | undefined;
@@ -109,6 +109,7 @@ export declare const ModelOverrideSchema: z.ZodObject<{
109
109
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
110
110
  contextWindow: z.ZodOptional<z.ZodNumber>;
111
111
  maxTokens: z.ZodOptional<z.ZodNumber>;
112
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
112
113
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
113
114
  compat: z.ZodOptional<z.ZodObject<{
114
115
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -336,6 +337,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
336
337
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
337
338
  contextWindow: z.ZodOptional<z.ZodNumber>;
338
339
  maxTokens: z.ZodOptional<z.ZodNumber>;
340
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
339
341
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
340
342
  compat: z.ZodOptional<z.ZodObject<{
341
343
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -449,6 +451,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
449
451
  premiumMultiplier: z.ZodOptional<z.ZodNumber>;
450
452
  contextWindow: z.ZodOptional<z.ZodNumber>;
451
453
  maxTokens: z.ZodOptional<z.ZodNumber>;
454
+ omitMaxOutputTokens: z.ZodOptional<z.ZodBoolean>;
452
455
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
453
456
  compat: z.ZodOptional<z.ZodObject<{
454
457
  supportsStore: z.ZodOptional<z.ZodBoolean>;
@@ -659,7 +659,7 @@ export declare const SETTINGS_SCHEMA: {
659
659
  readonly ui: {
660
660
  readonly tab: "appearance";
661
661
  readonly label: "Terminal Hyperlinks";
662
- readonly description: "Wrap file paths in OSC 8 hyperlinks for terminal-native click-to-open (auto: detect support; off: never; always: unconditional)";
662
+ readonly description: "Wrap paths and URLs in OSC 8 hyperlinks for terminal-native click-to-open (auto: detect support; off: never; always: unconditional)";
663
663
  };
664
664
  };
665
665
  readonly "display.tabWidth": {
@@ -745,6 +745,15 @@ export declare const SETTINGS_SCHEMA: {
745
745
  readonly description: "Render full tool descriptions in the system prompt instead of a tool name list";
746
746
  };
747
747
  };
748
+ readonly includeModelInPrompt: {
749
+ readonly type: "boolean";
750
+ readonly default: true;
751
+ readonly ui: {
752
+ readonly tab: "model";
753
+ readonly label: "Include Model In Prompt";
754
+ readonly description: "Surface the active model identifier in the system prompt so the agent knows which model it is";
755
+ };
756
+ };
748
757
  readonly temperature: {
749
758
  readonly type: "number";
750
759
  readonly default: -1;
@@ -2828,12 +2837,12 @@ export declare const SETTINGS_SCHEMA: {
2828
2837
  };
2829
2838
  readonly "tools.discoveryMode": {
2830
2839
  readonly type: "enum";
2831
- readonly values: readonly ["off", "mcp-only", "all"];
2832
- readonly default: "off";
2840
+ readonly values: readonly ["auto", "off", "mcp-only", "all"];
2841
+ readonly default: "auto";
2833
2842
  readonly ui: {
2834
2843
  readonly tab: "tools";
2835
2844
  readonly label: "Tool Discovery";
2836
- readonly description: "Hide tools behind a search tool to save tokens. 'mcp-only' hides MCP tools; 'all' hides all non-essential built-ins too.";
2845
+ readonly description: "Hide tools behind a search tool to save tokens. 'auto' hides MCP tools once the tool set has more than 40 tools; 'mcp-only' always hides MCP tools; 'all' hides all non-essential built-ins too.";
2837
2846
  };
2838
2847
  };
2839
2848
  readonly "tools.essentialOverride": {
@@ -132,6 +132,17 @@ export declare class Settings {
132
132
  * can register independently without overwriting each other.
133
133
  */
134
134
  export declare function onAppendOnlyModeChanged(cb: (value: string) => void): () => void;
135
+ /**
136
+ * Subscribe to changes in the Hindsight bank-scoping settings. Lets the
137
+ * Hindsight backend rebuild the active `HindsightSessionState` when the
138
+ * operator switches `hindsight.bankId`, `hindsight.bankIdPrefix`, or
139
+ * `hindsight.scoping` mid-session so subsequent retain/recall calls land in
140
+ * the new bank instead of the one selected at session start.
141
+ *
142
+ * Returns an unsubscribe function. The callback receives no arguments — the
143
+ * caller is expected to re-read the relevant settings via `Settings.get`.
144
+ */
145
+ export declare function onHindsightScopeChanged(cb: () => void): () => void;
135
146
  export declare function isSettingsInitialized(): boolean;
136
147
  /**
137
148
  * Reset the global singleton for testing.
@@ -102,6 +102,7 @@ export interface ParsedAgentFields {
102
102
  output?: unknown;
103
103
  thinkingLevel?: ThinkingLevel;
104
104
  autoloadSkills?: string[];
105
+ readSummarize?: boolean;
105
106
  blocking?: boolean;
106
107
  }
107
108
  /**
@@ -20,9 +20,8 @@ export declare function __computeBunfsPackageRoot(metaDir: string, pathImpl?: ty
20
20
  * and `__dirname`-relative `readFileSync` asset loads (HTML/CSS bundled next to
21
21
  * the entry) resolve exactly as they do under the original Pi runtime — no
22
22
  * temp-directory mirroring and no asset copying. An `onLoad` hook scoped to the
23
- * entry's relative-import graph rewrites only the legacy `@(scope)/pi-*` and
24
- * `@sinclair/typebox` imports in the extension's own source; everything else
25
- * resolves natively.
23
+ * entry's source graph rewrites only host-resolved compatibility imports in the
24
+ * extension's own source; everything else resolves natively.
26
25
  */
27
26
  export declare function loadLegacyPiModule(resolvedPath: string): Promise<unknown>;
28
27
  export declare function installLegacyPiSpecifierShim(): void;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bank ID derivation, project-tag scoping, and first-use mission setup.
2
+ * Bank ID derivation, project-tag scoping, and first-use bank setup.
3
3
  *
4
4
  * Three scoping modes (`HindsightConfig.scoping`):
5
5
  * - `global` — single shared bank, no per-project filter.
@@ -11,10 +11,13 @@
11
11
  * The base bank id is `bankIdPrefix-bankId` (default `omp`). Per-project mode
12
12
  * appends `-<project>`; tagged mode leaves the bank untouched and uses tags.
13
13
  *
14
- * Mission setup is idempotent at module level — a missionsSet keeps track of
15
- * banks we've already POSTed to so each session boundary doesn't fire a fresh
16
- * `createBank` call. Failures are swallowed: missions are an optimisation, not
17
- * a precondition for retain/recall.
14
+ * Bank existence is idempotent at module level — a banksSet keeps track of
15
+ * banks we've already PUT so each session boundary doesn't fire a fresh
16
+ * `createBank` call. The PUT is idempotent server-side, so re-firing on a hot
17
+ * path would only burn round-trips. Failures are swallowed: missing the
18
+ * mission patch is an optimisation, but the bank ITSELF must exist before
19
+ * mental-model bootstrap or the first retain, otherwise the very first POST
20
+ * lands against a missing bank.
18
21
  */
19
22
  import type { HindsightApi } from "./client";
20
23
  import type { HindsightConfig } from "./config";
@@ -46,9 +49,14 @@ export declare function computeBankScope(config: HindsightConfig, directory: str
46
49
  */
47
50
  export declare function deriveBankId(config: HindsightConfig, directory: string): string;
48
51
  /**
49
- * Ensure a bank's reflect/retain mission is set, exactly once per process.
52
+ * Ensure a bank exists, and patch its reflect/retain mission on first use.
50
53
  *
51
- * Tracked via the supplied set; on overflow we drop the oldest half so the set
52
- * cannot grow unboundedly across long-lived processes.
54
+ * Idempotent: skips the PUT when the bank id is already in the supplied set.
55
+ * The mission body is optional — when `bankMission` is blank we still PUT to
56
+ * make sure the bank itself is created, so mental-model bootstrap and the
57
+ * first retain don't land against a non-existent bank.
58
+ *
59
+ * The set is capped; on overflow we drop the oldest half so it cannot grow
60
+ * unboundedly across long-lived processes.
53
61
  */
54
- export declare function ensureBankMission(client: HindsightApi, bankId: string, config: HindsightConfig, missionsSet: Set<string>): Promise<void>;
62
+ export declare function ensureBankExists(client: HindsightApi, bankId: string, config: HindsightConfig, banksSet: Set<string>): Promise<void>;
@@ -70,7 +70,7 @@ export declare function resolveSeedsForScope(scope: BankScope, scoping: Hindsigh
70
70
  * Idempotently create any seed mental models that don't already exist on the
71
71
  * bank. Best-effort: a list/create failure does not throw — mental models are
72
72
  * an optimization, not a precondition for retain/recall, and we mirror the
73
- * swallow-on-failure pattern used by `ensureBankMission`.
73
+ * swallow-on-failure pattern used by `ensureBankExists`.
74
74
  *
75
75
  * Existing models are NEVER modified. See module docstring.
76
76
  */
@@ -19,12 +19,12 @@ export interface HindsightSessionStateOptions {
19
19
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
20
20
  config: HindsightConfig;
21
21
  session: AgentSession;
22
- missionsSet: Set<string>;
22
+ banksSet: Set<string>;
23
23
  lastRetainedTurn?: number;
24
24
  hasRecalledForFirstTurn?: boolean;
25
25
  /**
26
26
  * When set, this entry is a subagent alias that reuses the parent's bank,
27
- * scope, config, client, and missionsSet. Aliases skip auto-recall and
27
+ * scope, config, client, and banksSet. Aliases skip auto-recall and
28
28
  * auto-retain — those run on the parent only — but the recall/retain/reflect
29
29
  * tools resolve via the alias so they persist to the same bank as the parent.
30
30
  */
@@ -60,7 +60,7 @@ export declare class HindsightSessionState {
60
60
  recallTagsMatch?: "any" | "all" | "any_strict" | "all_strict";
61
61
  config: HindsightConfig;
62
62
  session: AgentSession;
63
- missionsSet: Set<string>;
63
+ banksSet: Set<string>;
64
64
  lastRetainedTurn: number;
65
65
  hasRecalledForFirstTurn: boolean;
66
66
  lastRecallSnippet?: string;
@@ -75,6 +75,12 @@ export declare class HindsightSessionState {
75
75
  */
76
76
  mentalModelsLoadPromise?: Promise<void>;
77
77
  unsubscribe?: () => void;
78
+ /**
79
+ * Releases the `onHindsightScopeChanged` subscription that drives live
80
+ * rebuilds when `hindsight.bankId` / `bankIdPrefix` / `scoping` change.
81
+ * Only set on primary states; aliases inherit the parent's subscription.
82
+ */
83
+ unsubscribeScope?: () => void;
78
84
  /** Alias states delegate persistence config to a primary parent state. */
79
85
  aliasOf?: HindsightSessionState;
80
86
  readonly retainQueue: HindsightRetainQueue;
@@ -1,4 +1,4 @@
1
- import type { TSchema } from "@oh-my-pi/pi-ai";
1
+ import { type TSchema } from "@oh-my-pi/pi-ai";
2
2
  import type { SourceMeta } from "../capability/types";
3
3
  import type { CustomTool } from "../extensibility/custom-tools/types";
4
4
  import type { AuthStorage } from "../session/auth-storage";
@@ -13,6 +13,17 @@ export declare class AssistantMessageComponent extends Container {
13
13
  constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: (() => void) | undefined, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget | undefined);
14
14
  invalidate(): void;
15
15
  setHideThinkingBlock(hide: boolean): void;
16
+ isTranscriptBlockFinalized(): boolean;
17
+ /**
18
+ * Assistant text/thinking streams in append-only: earlier rendered rows never
19
+ * re-layout, new content only grows the block at the bottom. The transcript
20
+ * reports this so the renderer may commit scrolled-off head rows of a long
21
+ * streamed reply to native scrollback instead of dropping them (see
22
+ * `NativeScrollbackLiveRegion#getNativeScrollbackCommitSafeEnd`). Volatile
23
+ * blocks (tool previews that collapse) intentionally do not implement this.
24
+ */
25
+ isTranscriptBlockAppendOnly(): boolean;
26
+ markTranscriptBlockFinalized(): void;
16
27
  setToolResultImages(toolCallId: string, images: ImageContent[]): void;
17
28
  setUsageInfo(usage: Usage): void;
18
29
  updateContent(message: AssistantMessage): void;
@@ -6,8 +6,10 @@ type ConfigurableEditorAction = Extract<AppKeybinding, "app.interrupt" | "app.cl
6
6
  */
7
7
  export declare class CustomEditor extends Editor {
8
8
  #private;
9
+ imageLinks?: readonly (string | undefined)[];
9
10
  /** Gradient-highlight the "ultrathink" / "orchestrate" / "workflow" keywords as the user types
10
- * them, skipping any occurrence inside code spans, fenced blocks, or XML sections. */
11
+ * them, skipping any occurrence inside code spans, fenced blocks, or XML sections. Also make
12
+ * pasted image placeholders visually distinct and hyperlink them once their blob file exists. */
11
13
  decorateText: (text: string) => string;
12
14
  onEscape?: () => void;
13
15
  onClear?: () => void;
@@ -0,0 +1,11 @@
1
+ import { Container } from "@oh-my-pi/pi-tui";
2
+ /**
3
+ * A persistent error banner pinned above the editor. Unlike the transcript
4
+ * "Error: …" line (which scrolls away as the conversation grows), this stays in
5
+ * the fixed region directly above the input so a turn that ended on a provider
6
+ * error — e.g. Anthropic's "Output blocked by content filtering policy" — cannot
7
+ * be missed. It is cleared when the next turn starts.
8
+ */
9
+ export declare class ErrorBannerComponent extends Container {
10
+ constructor(message: string);
11
+ }
@@ -52,6 +52,21 @@ export declare class ToolExecutionComponent extends Container {
52
52
  details?: any;
53
53
  isError?: boolean;
54
54
  }, isPartial?: boolean, _toolCallId?: string): void;
55
+ /**
56
+ * Whether this block has reached a terminal state for transcript freezing.
57
+ * Reports `false` while it can still visually change so the
58
+ * {@link TranscriptContainer} keeps it inside the repaintable live region:
59
+ * a foreground tool awaiting its result, or one streaming partial output.
60
+ * A final (non-partial) result, a background-async tool the agent has moved
61
+ * past, or an explicit {@link seal} flips it to `true`.
62
+ */
63
+ isTranscriptBlockFinalized(): boolean;
64
+ /**
65
+ * Mark the tool terminal even though no result arrived (the turn aborted or
66
+ * abandoned it) and stop animating, so it can freeze and stops pinning the
67
+ * transcript live region.
68
+ */
69
+ seal(): void;
55
70
  /**
56
71
  * Stop spinner animation and cleanup resources.
57
72
  */
@@ -1,4 +1,4 @@
1
- import { Container } from "@oh-my-pi/pi-tui";
1
+ import { Container, type NativeScrollbackLiveRegion } from "@oh-my-pi/pi-tui";
2
2
  /**
3
3
  * Transcript container that freezes the rendered output of every block except
4
4
  * the bottom-most (live) one on terminals where committed native scrollback is
@@ -21,10 +21,12 @@ import { Container } from "@oh-my-pi/pi-tui";
21
21
  * and any drift reconciles safely. On terminals that can rebuild history this
22
22
  * freezing is unnecessary, so it renders every block live for full fidelity.
23
23
  */
24
- export declare class TranscriptContainer extends Container {
24
+ export declare class TranscriptContainer extends Container implements NativeScrollbackLiveRegion {
25
25
  #private;
26
26
  invalidate(): void;
27
27
  clear(): void;
28
+ getNativeScrollbackLiveRegionStart(): number | undefined;
29
+ getNativeScrollbackCommitSafeEnd(): number | undefined;
28
30
  /**
29
31
  * Retire all frozen snapshots so the next render reflects each block's current
30
32
  * state. Call at reconciliation checkpoints (prompt submit) where the whole
@@ -3,6 +3,6 @@ import { Container } from "@oh-my-pi/pi-tui";
3
3
  * Component that renders a user message
4
4
  */
5
5
  export declare class UserMessageComponent extends Container {
6
- constructor(text: string, synthetic?: boolean);
6
+ constructor(text: string, synthetic?: boolean, imageLinks?: readonly (string | undefined)[]);
7
7
  render(width: number): string[];
8
8
  }
@@ -0,0 +1,17 @@
1
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import { type BlobPutResult } from "../session/blob-store";
3
+ type ImageBlobWriter = (data: Buffer, options?: {
4
+ extension?: string;
5
+ }) => Promise<BlobPutResult>;
6
+ type ImageBlobWriterSync = (data: Buffer, options?: {
7
+ extension?: string;
8
+ }) => BlobPutResult;
9
+ export interface ImageReferenceRenderers {
10
+ renderText: (text: string) => string;
11
+ renderReference: (label: string, index: number) => string;
12
+ }
13
+ export declare function renderImageReferences(text: string, renderers: ImageReferenceRenderers): string;
14
+ export declare function imageReferenceHyperlink(label: string, index: number, imageLinks: readonly (string | undefined)[] | undefined, renderLabel: (text: string) => string): string;
15
+ export declare function materializeImageReferenceLinks(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriter): Promise<(string | undefined)[] | undefined>;
16
+ export declare function materializeImageReferenceLinksSync(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriterSync): (string | undefined)[] | undefined;
17
+ export {};