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

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 (80) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/cli/update-cli.d.ts +15 -1
  3. package/dist/types/config/append-only-context-mode.d.ts +8 -0
  4. package/dist/types/config/model-registry.d.ts +3 -0
  5. package/dist/types/config/models-config-schema.d.ts +15 -0
  6. package/dist/types/config/settings-schema.d.ts +3 -3
  7. package/dist/types/exa/mcp-client.d.ts +2 -1
  8. package/dist/types/mcp/json-rpc.d.ts +6 -1
  9. package/dist/types/mcp/tool-bridge.d.ts +4 -0
  10. package/dist/types/mnemopi/state.d.ts +2 -2
  11. package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
  12. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -0
  13. package/dist/types/modes/components/plugin-settings.d.ts +40 -8
  14. package/dist/types/modes/components/session-selector.d.ts +8 -3
  15. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  16. package/dist/types/modes/utils/keybinding-matchers.d.ts +4 -0
  17. package/dist/types/session/agent-session.d.ts +4 -1
  18. package/dist/types/session/history-storage.d.ts +3 -4
  19. package/dist/types/session/messages.d.ts +1 -0
  20. package/dist/types/session/session-manager.d.ts +1 -0
  21. package/dist/types/slash-commands/types.d.ts +17 -4
  22. package/dist/types/tiny/text.d.ts +17 -0
  23. package/dist/types/web/search/providers/base.d.ts +14 -0
  24. package/dist/types/web/search/providers/exa.d.ts +9 -0
  25. package/dist/types/web/search/providers/perplexity.d.ts +2 -2
  26. package/dist/types/web/search/types.d.ts +2 -1
  27. package/package.json +9 -9
  28. package/src/cli/session-picker.ts +1 -0
  29. package/src/cli/update-cli.ts +54 -2
  30. package/src/commands/completions.ts +1 -1
  31. package/src/config/append-only-context-mode.ts +37 -0
  32. package/src/config/models-config-schema.ts +1 -0
  33. package/src/config/settings-schema.ts +2 -2
  34. package/src/exa/mcp-client.ts +11 -5
  35. package/src/internal-urls/docs-index.generated.ts +1 -1
  36. package/src/main.ts +4 -2
  37. package/src/mcp/json-rpc.ts +8 -0
  38. package/src/mcp/render.ts +3 -0
  39. package/src/mcp/tool-bridge.ts +10 -2
  40. package/src/mcp/transports/http.ts +33 -16
  41. package/src/mnemopi/state.ts +4 -4
  42. package/src/modes/acp/acp-agent.ts +168 -3
  43. package/src/modes/components/agent-dashboard.ts +103 -31
  44. package/src/modes/components/extensions/extension-dashboard.ts +56 -10
  45. package/src/modes/components/history-search.ts +128 -14
  46. package/src/modes/components/plugin-settings.ts +270 -36
  47. package/src/modes/components/session-selector.ts +45 -14
  48. package/src/modes/components/settings-selector.ts +1 -1
  49. package/src/modes/components/tips.txt +5 -1
  50. package/src/modes/components/transcript-container.ts +22 -4
  51. package/src/modes/controllers/command-controller.ts +4 -3
  52. package/src/modes/controllers/input-controller.ts +10 -5
  53. package/src/modes/controllers/selector-controller.ts +30 -19
  54. package/src/modes/interactive-mode.ts +38 -3
  55. package/src/modes/utils/keybinding-matchers.ts +10 -0
  56. package/src/prompts/steering/user-interjection.md +10 -0
  57. package/src/prompts/system/agent-creation-architect.md +1 -26
  58. package/src/prompts/system/system-prompt.md +143 -145
  59. package/src/prompts/system/title-system.md +3 -2
  60. package/src/prompts/tools/browser.md +29 -29
  61. package/src/prompts/tools/render-mermaid.md +2 -2
  62. package/src/sdk.ts +5 -21
  63. package/src/session/agent-session.ts +30 -7
  64. package/src/session/history-storage.ts +11 -18
  65. package/src/session/messages.ts +80 -0
  66. package/src/session/session-manager.ts +7 -1
  67. package/src/slash-commands/types.ts +27 -10
  68. package/src/tiny/text.ts +112 -1
  69. package/src/tools/memory-recall.ts +1 -1
  70. package/src/tools/memory-reflect.ts +1 -1
  71. package/src/tools/ssh.ts +26 -10
  72. package/src/tools/write.ts +14 -2
  73. package/src/tui/status-line.ts +15 -4
  74. package/src/utils/title-generator.ts +9 -2
  75. package/src/web/search/index.ts +3 -1
  76. package/src/web/search/provider.ts +1 -1
  77. package/src/web/search/providers/base.ts +17 -0
  78. package/src/web/search/providers/exa.ts +111 -7
  79. package/src/web/search/providers/perplexity.ts +8 -4
  80. package/src/web/search/types.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.9.1] - 2026-06-04
6
+
7
+ ### Added
8
+
9
+ - Added deferred session-title generation so greetings no longer become the session title. A first user message that is only a greeting / acknowledgement / filler ("hi", "thanks", "ok", a bare number, emoji-only, etc.) is now detected deterministically and skips titling entirely — no title model is invoked. Title generation then retries on each subsequent user message while the session stays unnamed, so the title is deduced from the first message that actually describes work. A capable online title model may additionally answer `none` to decline a non-greeting taskless message (normalized to "no title").
10
+
11
+ ### Changed
12
+
13
+ - Changed mid-turn user steers to reach the model inside a wire-only interjection envelope, while transcripts and persisted session history keep the user's original text.
14
+ - Changed the system prompt to treat user requests for parallel work as `task` subagent fan-out rather than parallel tool calls.
15
+ - Changed the Agent Control Center's new-agent description field to use the multiline TUI editor, with Enter inserting lines and Ctrl+Enter generating the spec.
16
+ - Changed the Agent Control Center and Extension Control Center to accept Left/Right arrow keys for switching tabs (source / provider), in addition to Tab / Shift+Tab — matching the model and settings selectors, whose `TabBar` already supported arrow navigation.
17
+ - Refreshed the Ctrl+R history search overlay: the selected row now renders as a full-width `selectedBg` highlight bar, matched query tokens are highlighted in the accent color, each result shows a right-aligned relative timestamp, and the panel gained an icon'd accent title plus a two-tone keyhint footer. The selector also gained PageUp/PageDown (via the configurable `tui.select.pageUp`/`pageDown` keybindings) and Home/End navigation.
18
+ - Changed Perplexity API-key web search to return more comprehensive results: `web_search_options.search_context_size` is now `high` (was `medium`) for maximum retrieval grounding, the default `num_search_results` is `20` (was `10`) so twice as many sources are surfaced, and `return_related_questions` is enabled with the response's `related_questions` now parsed into `relatedQuestions` (previously dropped). On an identical query this lifted the result from 10 sources / ~410 output tokens to 20 sources / ~1900 output tokens with a structured, multi-section answer; latency tracks model output length, not context size, so the 60s hard timeout headroom is unchanged.
19
+
20
+ ### Fixed
21
+
22
+ - 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
+ - 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
+ - Fixed `omp completions` to await the completion script write before exiting.
26
+ - Fixed `AssistantMessageComponent` exposing its stable-prefix completion API again so streamed assistant messages remain unstable until explicitly completed.
27
+ - Fixed session restoration to ignore transient fallback model switches (such as automatic context-promotion or retry fallback) so resumed or resumed-switch sessions revert to the configured default model unless the last change was a user-selected temporary model
28
+ - Fixed in-session `/resume` to restore both the last user-selected temporary model and persisted plan/goal mode state instead of falling back to the default model with plan mode off.
29
+ - Fixed the `/resume` session picker overflowing short viewports: the visible window was hardcoded to 5 entries (and assumed 3 lines each), but titled sessions render 4 lines, so on a typical-height terminal the picker's header and search box scrolled off the top and the first entry was hidden until you scrolled the terminal up. The visible-entry count is now derived from the live terminal height (budgeting the worst-case 4-line titled entry plus the picker's chrome), so the whole picker fits the viewport and grows on taller terminals.
30
+ - Fixed the Agent Control Center and Extension Control Center dashboards overflowing the terminal: they were mounted inline below the chat transcript, so the combined height exceeded the viewport — the tab bar and controls scrolled off the top into native scrollback, and every state change yanked the view back to the bottom. Both dashboards now render as full-screen overlays sized to the live terminal height (`process.stdout.rows`), re-fit on resize, fill the viewport, and reserve space for the footer keyhints so the controls stay visible.
31
+ - Fixed Ctrl+R history search results to remain globally sorted by prompt recency after merging FTS prefix matches with substring fallback matches.
32
+ - Fixed Exa web search with no stored or environment credential to use the public Exa MCP fallback again, preserving the auth storage → `EXA_API_KEY` → `mcp.exa.ai` resolution order ([#1860](https://github.com/can1357/oh-my-pi/issues/1860)).
33
+ - Fixed ACP plan-mode writes to `local://PLAN.md` so session-local plan artifacts are written to OMP's local artifact root instead of being routed through the editor `writeTextFile` bridge, avoiding Zen's `Internal error` and making the plan readable after creation ([#1863](https://github.com/can1357/oh-my-pi/issues/1863)).
34
+ - Fixed ACP plan mode stranding the agent at plan approval: entering `mode: "plan"` now registers a standing `resolve` handler so the agent's `resolve { action: "apply" }` no longer fails with `No pending action to resolve. Nothing to apply or discard.` The handler validates the plan file, asks the ACP client to confirm via `unstable_createElicitation` when the client supports forms, renames the approved plan to `local://<title>.md`, and exits plan mode so the agent regains write tools for execution ([#1869](https://github.com/can1357/oh-my-pi/issues/1869)).
35
+
36
+ - Fixed `provider.appendOnlyContext: "auto"` staying inactive for Xiaomi Token Plan/SGLang endpoints, preserving prefix-cache hits without forcing append-only mode globally ([#1851](https://github.com/can1357/oh-my-pi/issues/1851)).
37
+ - Fixed `models.yml` compatibility parsing to preserve `compat.cacheControlFormat: "anthropic"` for custom OpenAI-compatible Claude proxies. ([#1845](https://github.com/can1357/oh-my-pi/issues/1845))
38
+ - Fixed the TUI's `Settings → Plugins` panel reporting "No plugins installed" when only marketplace plugins were installed. The panel now merges `PluginManager.list()` with `MarketplaceManager.listInstalledPlugins()` — the same data source the `/plugins list` slash command and `omp plugin list` CLI already used — and tags each row with an `[npm]` / `[marketplace]` kind badge, a scope tag, and a shadow indicator for project-shadowed user installs. Selecting a marketplace row opens a new `MarketplacePluginDetailComponent` whose single `Enabled` toggle calls `MarketplaceManager.setPluginEnabled(pluginId, enabled, scope)`, with read-only metadata (version, install path, installed-at, last-updated, git commit SHA) listed below the toggle. The empty-state now lists both install commands (`omp plugin install <package>` and `omp plugin install <name>@<marketplace>`) ([#1842](https://github.com/can1357/oh-my-pi/issues/1842)).
39
+ - Fixed scoped mnemopi recall in `MnemopiSessionState.collectScopedRecallResults`/`recallResultsScoped` to await the async `Mnemopi.recallEnhanced` so the new auto-derived `queryEmbedding` flows through. Without this, the embedding-enabled mnemopi backend silently kept running FTS-only on every recall. ([#1832](https://github.com/can1357/oh-my-pi/issues/1832))
40
+ - Fixed the SSH tool renderer inlining multiline remote commands into its single-line status header, which produced a malformed cell where the bordered output block opened mid-command. The renderer now drops the command from the header (which keeps only `[host]`) and renders the full command in a framed section above `Output`, mirroring the bash renderer. `renderStatusLine` also flattens any embedded CR/LF in `description`, `meta`, and `title` so no tool can accidentally expand the header into multiple rows ([#1828](https://github.com/can1357/oh-my-pi/issues/1828)).
41
+ - Fixed `tsc --noEmit` against `packages/coding-agent/tsconfig.json` reporting 56 errors under TypeScript 5.x (`builtin-registry.ts` × 46, `agent-session-openai-responses-replay.test.ts` × 10). The repo's own gate (`tsgo` / TypeScript 6.x) already accepted the `() => void` slash-command handlers, but 5.x rejects them because it does not coerce a `void`-returning function value into a `() => T | undefined` slot. The `SlashCommandSpec.handle` / `handleTui` signatures and the test's `createPersistedSession` `populate` callback are now expressed as a union of two function types (one returning a `SlashCommandResult` / target, one returning `void`), so the existing handler bodies typecheck on both compilers ([#1821](https://github.com/can1357/oh-my-pi/issues/1821)).
42
+ - Fixed `omp update` leaving `@oh-my-pi/pi-natives` and the platform-specific `@oh-my-pi/pi-natives-<tag>` leaf at the previous version on `bun install -g` updates, so the next launch loaded a stale `.node` file and aborted at `validateLoadedBindings` with `The .node file on disk is from a different release than this loader`. `omp update` now pins the native addon core and the platform leaf to the same version it installs for `@oh-my-pi/pi-coding-agent` ([#1824](https://github.com/can1357/oh-my-pi/issues/1824)).
43
+
5
44
  ## [15.9.0] - 2026-06-04
6
45
 
7
46
  ### Breaking Changes
@@ -40,8 +40,22 @@ export declare function replaceBinaryForUpdate(options: BinaryReplacementOptions
40
40
  *
41
41
  * Together these two flags make `omp update` produce exactly the registry
42
42
  * lookup the version check just performed. See #1686.
43
+ *
44
+ * Also pins {@link NATIVES_PACKAGE} and the platform-specific
45
+ * `@oh-my-pi/pi-natives-<tag>` leaf to `expectedVersion`. `bun install -g`
46
+ * does not reliably refresh transitive `optionalDependencies` when the
47
+ * top-level package is the only one bumped, so the native addon and its
48
+ * version sentinel can drift out of sync with the freshly installed
49
+ * `@oh-my-pi/pi-coding-agent` and the loader aborts at
50
+ * `validateLoadedBindings` on the next launch
51
+ * (`The .node file on disk is from a different release than this loader`).
52
+ * Listing the natives explicitly forces bun to replace them in lock-step.
53
+ * The leaf is added only on tags the release pipeline actually publishes
54
+ * ({@link SUPPORTED_NATIVE_TAGS}) so unsupported platforms still fail with
55
+ * the original "no matching version" message instead of `EBADPLATFORM`.
56
+ * See #1824.
43
57
  */
44
- export declare function buildBunInstallArgs(expectedVersion: string): string[];
58
+ export declare function buildBunInstallArgs(expectedVersion: string, nativeTag?: string): string[];
45
59
  /**
46
60
  * Run the update command.
47
61
  */
@@ -0,0 +1,8 @@
1
+ /** Provider metadata needed to resolve append-only context mode. */
2
+ export interface AppendOnlyContextModel {
3
+ provider: string;
4
+ baseUrl: string;
5
+ compat?: object;
6
+ }
7
+ /** Resolves whether append-only context should be active for a model and setting. */
8
+ export declare function shouldEnableAppendOnlyContext(setting: "auto" | "on" | "off" | undefined, model: AppendOnlyContextModel | null | undefined): boolean;
@@ -72,6 +72,7 @@ export declare const ModelsConfigFile: ConfigFile<{
72
72
  order?: string[] | undefined;
73
73
  } | undefined;
74
74
  extraBody?: Record<string, unknown> | undefined;
75
+ cacheControlFormat?: "anthropic" | undefined;
75
76
  supportsStrictMode?: boolean | undefined;
76
77
  toolStrictMode?: "all_strict" | "none" | undefined;
77
78
  } | undefined;
@@ -139,6 +140,7 @@ export declare const ModelsConfigFile: ConfigFile<{
139
140
  order?: string[] | undefined;
140
141
  } | undefined;
141
142
  extraBody?: Record<string, unknown> | undefined;
143
+ cacheControlFormat?: "anthropic" | undefined;
142
144
  supportsStrictMode?: boolean | undefined;
143
145
  toolStrictMode?: "all_strict" | "none" | undefined;
144
146
  } | undefined;
@@ -200,6 +202,7 @@ export declare const ModelsConfigFile: ConfigFile<{
200
202
  order?: string[] | undefined;
201
203
  } | undefined;
202
204
  extraBody?: Record<string, unknown> | undefined;
205
+ cacheControlFormat?: "anthropic" | undefined;
203
206
  supportsStrictMode?: boolean | undefined;
204
207
  toolStrictMode?: "all_strict" | "none" | undefined;
205
208
  } | undefined;
@@ -47,6 +47,9 @@ export declare const OpenAICompatSchema: z.ZodObject<{
47
47
  order: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
48
  }, z.core.$strip>>;
49
49
  extraBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
50
+ cacheControlFormat: z.ZodOptional<z.ZodEnum<{
51
+ anthropic: "anthropic";
52
+ }>>;
50
53
  supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
51
54
  toolStrictMode: z.ZodOptional<z.ZodEnum<{
52
55
  all_strict: "all_strict";
@@ -155,6 +158,9 @@ export declare const ModelOverrideSchema: z.ZodObject<{
155
158
  order: z.ZodOptional<z.ZodArray<z.ZodString>>;
156
159
  }, z.core.$strip>>;
157
160
  extraBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
161
+ cacheControlFormat: z.ZodOptional<z.ZodEnum<{
162
+ anthropic: "anthropic";
163
+ }>>;
158
164
  supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
159
165
  toolStrictMode: z.ZodOptional<z.ZodEnum<{
160
166
  all_strict: "all_strict";
@@ -242,6 +248,9 @@ export declare const ModelsConfigSchema: z.ZodObject<{
242
248
  order: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
249
  }, z.core.$strip>>;
244
250
  extraBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
251
+ cacheControlFormat: z.ZodOptional<z.ZodEnum<{
252
+ anthropic: "anthropic";
253
+ }>>;
245
254
  supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
246
255
  toolStrictMode: z.ZodOptional<z.ZodEnum<{
247
256
  all_strict: "all_strict";
@@ -376,6 +385,9 @@ export declare const ModelsConfigSchema: z.ZodObject<{
376
385
  order: z.ZodOptional<z.ZodArray<z.ZodString>>;
377
386
  }, z.core.$strip>>;
378
387
  extraBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
388
+ cacheControlFormat: z.ZodOptional<z.ZodEnum<{
389
+ anthropic: "anthropic";
390
+ }>>;
379
391
  supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
380
392
  toolStrictMode: z.ZodOptional<z.ZodEnum<{
381
393
  all_strict: "all_strict";
@@ -486,6 +498,9 @@ export declare const ModelsConfigSchema: z.ZodObject<{
486
498
  order: z.ZodOptional<z.ZodArray<z.ZodString>>;
487
499
  }, z.core.$strip>>;
488
500
  extraBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
501
+ cacheControlFormat: z.ZodOptional<z.ZodEnum<{
502
+ anthropic: "anthropic";
503
+ }>>;
489
504
  supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
490
505
  toolStrictMode: z.ZodOptional<z.ZodEnum<{
491
506
  all_strict: "all_strict";
@@ -3331,7 +3331,7 @@ export declare const SETTINGS_SCHEMA: {
3331
3331
  }, {
3332
3332
  readonly value: "exa";
3333
3333
  readonly label: "Exa";
3334
- readonly description: "Requires EXA_API_KEY";
3334
+ readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
3335
3335
  }, {
3336
3336
  readonly value: "parallel";
3337
3337
  readonly label: "Parallel";
@@ -3668,11 +3668,11 @@ export declare const SETTINGS_SCHEMA: {
3668
3668
  readonly ui: {
3669
3669
  readonly tab: "providers";
3670
3670
  readonly label: "Append-Only Context";
3671
- readonly description: "Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Anthropic) hit at maximum rate. Auto enables for DeepSeek.";
3671
+ readonly description: "Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Xiaomi/SGLang, Anthropic) hit at maximum rate. Auto enables for known prefix-cache providers.";
3672
3672
  readonly options: readonly [{
3673
3673
  readonly value: "auto";
3674
3674
  readonly label: "Auto";
3675
- readonly description: "Enable for DeepSeek (recommended)";
3675
+ readonly description: "Enable for known prefix-cache providers (recommended)";
3676
3676
  }, {
3677
3677
  readonly value: "on";
3678
3678
  readonly label: "On";
@@ -1,5 +1,6 @@
1
1
  import type { TSchema } from "@oh-my-pi/pi-ai";
2
2
  import type { CustomTool, CustomToolResult } from "../extensibility/custom-tools/types";
3
+ import { type CallMcpOptions } from "../mcp/json-rpc";
3
4
  import type { ExaRenderDetails, ExaSearchResponse, MCPTool, MCPToolWrapperConfig } from "./types";
4
5
  /** Find EXA_API_KEY from Bun.env or .env files */
5
6
  export declare function findApiKey(): string | null;
@@ -8,7 +9,7 @@ export declare function fetchExaTools(apiKey: string | null, toolNames: string[]
8
9
  /** Fetch available tools from Websets MCP */
9
10
  export declare function fetchWebsetsTools(apiKey: string): Promise<MCPTool[]>;
10
11
  /** Call a tool on Exa MCP (simplified: toolName as first arg for easier use) */
11
- export declare function callExaTool(toolName: string, args: Record<string, unknown>, apiKey: string | null): Promise<unknown>;
12
+ export declare function callExaTool(toolName: string, args: Record<string, unknown>, apiKey: string | null, options?: CallMcpOptions): Promise<unknown>;
12
13
  /** Call a tool on Websets MCP */
13
14
  export declare function callWebsetsTool(apiKey: string, toolName: string, args: Record<string, unknown>): Promise<unknown>;
14
15
  /** Format search results for LLM */
@@ -11,12 +11,17 @@ export interface JsonRpcResponse<T = unknown> {
11
11
  data?: unknown;
12
12
  };
13
13
  }
14
+ /** Options controlling a single MCP JSON-RPC HTTP request. */
15
+ export interface CallMcpOptions {
16
+ signal?: AbortSignal;
17
+ }
14
18
  /**
15
19
  * Call an MCP server with JSON-RPC 2.0 over HTTPS.
16
20
  *
17
21
  * @param url - Full MCP server URL (including any query parameters)
18
22
  * @param method - JSON-RPC method name (e.g., "tools/list", "tools/call")
19
23
  * @param params - Method parameters
24
+ * @param options - Optional transport controls such as cancellation.
20
25
  * @returns Parsed JSON-RPC response
21
26
  */
22
- export declare function callMCP<T = unknown>(url: string, method: string, params?: Record<string, unknown>): Promise<JsonRpcResponse<T>>;
27
+ export declare function callMCP<T = unknown>(url: string, method: string, params?: Record<string, unknown>, options?: CallMcpOptions): Promise<JsonRpcResponse<T>>;
@@ -53,6 +53,8 @@ export declare class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
53
53
  readonly mcpToolName: string;
54
54
  /** Server name */
55
55
  readonly mcpServerName: string;
56
+ /** Render completed MCP calls with the result header replacing the pending call header. */
57
+ readonly mergeCallAndResult = true;
56
58
  /** Create MCPTool instances for all tools from an MCP server connection */
57
59
  static fromTools(connection: MCPServerConnection, tools: MCPToolDefinition[], reconnect?: MCPReconnect): MCPTool[];
58
60
  constructor(connection: MCPServerConnection, tool: MCPToolDefinition, reconnect?: MCPReconnect | undefined);
@@ -77,6 +79,8 @@ export declare class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetai
77
79
  readonly mcpToolName: string;
78
80
  /** Server name */
79
81
  readonly mcpServerName: string;
82
+ /** Render completed MCP calls with the result header replacing the pending call header. */
83
+ readonly mergeCallAndResult = true;
80
84
  /** Create DeferredMCPTool instances for all tools from an MCP server */
81
85
  static fromTools(serverName: string, tools: MCPToolDefinition[], getConnection: () => Promise<MCPServerConnection>, source?: SourceMeta, reconnect?: MCPReconnect): DeferredMCPTool[];
82
86
  constructor(serverName: string, tool: MCPToolDefinition, getConnection: () => Promise<MCPServerConnection>, source?: SourceMeta, reconnect?: MCPReconnect | undefined);
@@ -48,8 +48,8 @@ export declare class MnemopiSessionState {
48
48
  getScopedRetainTarget(): MnemopiScopedMemory;
49
49
  editScopedMemory(op: MnemopiMemoryEditOperation, id: string, options?: MnemopiMemoryEditOptions): MnemopiMemoryEditResult;
50
50
  formatScopedRecallWithIds(results: readonly RecallResult[]): string;
51
- collectScopedRecallResults(query: string): RecallResult[];
52
- recallResultsScoped(query: string): RecallResult[];
51
+ collectScopedRecallResults(query: string): Promise<RecallResult[]>;
52
+ recallResultsScoped(query: string): Promise<RecallResult[]>;
53
53
  formatScopedRecallContext(results: readonly RecallResult[], format?: "bullet" | "json"): string | undefined;
54
54
  formatContextScoped(results: readonly RecallResult[], format?: "bullet" | "json"): string;
55
55
  rememberInScope(memory: MnemopiRememberInput, options?: MnemopiRememberOptions): string | undefined;
@@ -16,6 +16,7 @@ export declare class AgentDashboard extends Container {
16
16
  onRequestRender?: () => void;
17
17
  private constructor();
18
18
  static create(cwd: string, settings?: Settings | null, terminalHeight?: number, modelContext?: AgentDashboardModelContext): Promise<AgentDashboard>;
19
+ render(width: number): string[];
19
20
  handleInput(data: string): void;
20
21
  }
21
22
  export {};
@@ -22,5 +22,6 @@ export declare class ExtensionDashboard extends Container {
22
22
  onRequestRender?: () => void;
23
23
  private constructor();
24
24
  static create(cwd: string, settings?: Settings | null, terminalHeight?: number): Promise<ExtensionDashboard>;
25
+ render(width: number): string[];
25
26
  handleInput(data: string): void;
26
27
  }
@@ -2,13 +2,15 @@
2
2
  * Plugin settings UI components.
3
3
  *
4
4
  * Provides a hierarchical settings interface:
5
- * - Plugin list (shows all installed plugins)
6
- * - Plugin detail (enable/disable, features, config)
5
+ * - Plugin list (npm plugins + marketplace plugins)
6
+ * - npm plugin detail (enable/disable, features, config)
7
+ * - Marketplace plugin detail (enable/disable + read-only metadata)
7
8
  * - Feature toggles
8
9
  * - Config value editor
9
10
  */
10
11
  import { Container } from "@oh-my-pi/pi-tui";
11
12
  import { PluginManager } from "../../extensibility/plugins/manager";
13
+ import type { InstalledPluginSummary } from "../../extensibility/plugins/marketplace";
12
14
  import type { InstalledPlugin } from "../../extensibility/plugins/types";
13
15
  /**
14
16
  * Forwards a keystroke to `input`, but cancels via `onCancel` when the user presses Escape.
@@ -16,18 +18,32 @@ import type { InstalledPlugin } from "../../extensibility/plugins/types";
16
18
  export declare function handleInputOrEscape(data: string, input: {
17
19
  handleInput(data: string): void;
18
20
  }, onCancel: () => void): void;
21
+ /**
22
+ * One row in the unified plugin list. npm and marketplace plugins live in
23
+ * separate registries with different shapes, so a tagged union keeps both
24
+ * paths type-safe end-to-end (list rendering, value lookup, detail callback).
25
+ */
26
+ export type PluginListEntry = {
27
+ kind: "npm";
28
+ plugin: InstalledPlugin;
29
+ } | {
30
+ kind: "marketplace";
31
+ plugin: InstalledPluginSummary;
32
+ };
19
33
  export interface PluginListCallbacks {
20
- onPluginSelect: (plugin: InstalledPlugin) => void;
34
+ onNpmSelect: (plugin: InstalledPlugin) => void;
35
+ onMarketplaceSelect: (plugin: InstalledPluginSummary) => void;
21
36
  onCancel: () => void;
22
37
  }
23
38
  /**
24
- * Shows list of installed plugins with enable/disable status.
25
- * Selecting a plugin opens its detail view.
39
+ * Shows installed plugins from both registries (npm + marketplace) with
40
+ * enable/disable status, scope tag, and shadow indicator. Selecting an entry
41
+ * fans out to the kind-specific detail callback.
26
42
  */
27
43
  export declare class PluginListComponent extends Container {
28
44
  #private;
29
- private readonly plugins;
30
- constructor(plugins: InstalledPlugin[], callbacks: PluginListCallbacks);
45
+ private readonly entries;
46
+ constructor(entries: ReadonlyArray<PluginListEntry>, callbacks: PluginListCallbacks);
31
47
  handleInput(data: string): void;
32
48
  }
33
49
  export interface PluginDetailCallbacks {
@@ -50,9 +66,25 @@ export declare class PluginDetailComponent extends Container {
50
66
  constructor(plugin: InstalledPlugin, manager: PluginManager, callbacks: PluginDetailCallbacks);
51
67
  handleInput(data: string): void;
52
68
  }
69
+ export interface MarketplacePluginDetailCallbacks {
70
+ onEnabledChange: (enabled: boolean) => void;
71
+ onBack: () => void;
72
+ }
73
+ /**
74
+ * Detail view for a marketplace plugin. Marketplace plugins do not declare
75
+ * features or settings, so the panel exposes a single enable/disable toggle
76
+ * plus the read-only metadata from the installed-plugins registry.
77
+ */
78
+ export declare class MarketplacePluginDetailComponent extends Container {
79
+ #private;
80
+ private plugin;
81
+ private readonly callbacks;
82
+ constructor(plugin: InstalledPluginSummary, callbacks: MarketplacePluginDetailCallbacks);
83
+ handleInput(data: string): void;
84
+ }
53
85
  export interface PluginSettingsCallbacks {
54
86
  onClose: () => void;
55
- onPluginChanged: () => void;
87
+ onPluginChanged: () => void | Promise<void>;
56
88
  }
57
89
  /**
58
90
  * Top-level plugin settings component.
@@ -8,10 +8,10 @@ export type SessionHistoryMatcher = (query: string) => string[];
8
8
  *
9
9
  * - `fuzzy` is the ordered fuzzy-filter result over session metadata (best first).
10
10
  * - `historyIds` are session IDs whose recorded prompts matched the query,
11
- * ordered by history relevance (best first); duplicates are tolerated.
11
+ * ordered by prompt-history rank (typically newest matching prompt first); duplicates are tolerated.
12
12
  *
13
13
  * Ranking: sessions matched by **both** signals lead (keeping fuzzy order), then
14
- * fuzzy-only matches, then history-only matches (by history order). A fuzzy match
14
+ * fuzzy-only matches, then history-only matches (by prompt-history order). A fuzzy match
15
15
  * is never dropped, and history matches not present in `allSessions` (e.g. deleted
16
16
  * or out-of-scope sessions) are ignored since they cannot be resumed from here.
17
17
  */
@@ -26,7 +26,7 @@ declare class SessionList implements Component {
26
26
  onExit: () => void;
27
27
  onToggleScope?: () => void;
28
28
  onDeleteRequest?: (session: SessionInfo) => void;
29
- constructor(sessions: SessionInfo[], showCwd?: boolean, historyMatcher?: SessionHistoryMatcher);
29
+ constructor(sessions: SessionInfo[], showCwd?: boolean, historyMatcher?: SessionHistoryMatcher, getTerminalRows?: () => number);
30
30
  /** Replace the visible dataset, e.g. when toggling folder/all-projects scope. */
31
31
  setSessions(sessions: SessionInfo[], showCwd: boolean): void;
32
32
  removeSession(sessionPath: string): void;
@@ -43,6 +43,11 @@ export interface SessionSelectorOptions {
43
43
  allSessions?: SessionInfo[];
44
44
  /** Open directly in all-projects scope (e.g. the current folder has no sessions). */
45
45
  startInAllScope?: boolean;
46
+ /**
47
+ * Reads the live terminal height so the visible window fits the viewport.
48
+ * Omitted only in tests; defaults to a conservative 24 rows.
49
+ */
50
+ getTerminalRows?: () => number;
46
51
  }
47
52
  /**
48
53
  * Component that renders a session selector with optional confirmation dialog
@@ -36,7 +36,7 @@ export interface SettingsCallbacks {
36
36
  /** Get current rendered status line for inline preview */
37
37
  getStatusLinePreview?: () => string;
38
38
  /** Called when plugins change */
39
- onPluginsChanged?: () => void;
39
+ onPluginsChanged?: () => void | Promise<void>;
40
40
  /** Called when settings panel is closed */
41
41
  onCancel: () => void;
42
42
  }
@@ -12,4 +12,8 @@ export declare function matchesSelectCancel(data: string): boolean;
12
12
  export declare function matchesSelectUp(data: string): boolean;
13
13
  /** Match the generic selector down-navigation keybinding. */
14
14
  export declare function matchesSelectDown(data: string): boolean;
15
+ /** Match the generic selector page-up keybinding. */
16
+ export declare function matchesSelectPageUp(data: string): boolean;
17
+ /** Match the generic selector page-down keybinding. */
18
+ export declare function matchesSelectPageDown(data: string): boolean;
15
19
  export declare function matchesAppExternalEditor(data: string): boolean;
@@ -313,6 +313,7 @@ export declare class AgentSession {
313
313
  peekQueueInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
314
314
  peekStandingResolveHandler(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
315
315
  setStandingResolveHandler(handler: ((input: unknown) => Promise<unknown> | unknown) | null): void;
316
+ setSessionSwitchReconciler(reconciler: (() => Promise<void>) | null): void;
316
317
  /** Provider-scoped mutable state store for transport/session caches. */
317
318
  get providerSessionState(): Map<string, ProviderSessionState>;
318
319
  getHindsightSessionState(): HindsightSessionState | undefined;
@@ -637,7 +638,9 @@ export declare class AgentSession {
637
638
  * Validates API key, saves to session log but NOT to settings.
638
639
  * @throws Error if no API key available for the model
639
640
  */
640
- setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel): Promise<void>;
641
+ setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel, options?: {
642
+ ephemeral?: boolean;
643
+ }): Promise<void>;
641
644
  /**
642
645
  * Cycle to next/previous model.
643
646
  * Uses scoped models (from --models flag) if available, otherwise all available models.
@@ -22,10 +22,9 @@ export declare class HistoryStorage {
22
22
  getRecent(limit: number): HistoryEntry[];
23
23
  search(query: string, limit: number): HistoryEntry[];
24
24
  /**
25
- * IDs of the sessions whose stored prompts match `query`, ordered by match
26
- * relevance (most relevant/recent first) and de-duplicated. Prompts with no
27
- * recorded session are skipped. Used to augment session ranking in the
28
- * resume picker with prompts that the 4KB session-list prefix never sees.
25
+ * IDs of the sessions whose stored prompts match `query`, ordered by prompt
26
+ * recency and de-duplicated. Used to augment session ranking in the resume
27
+ * picker with prompts that the 4KB session-list prefix never sees.
29
28
  */
30
29
  matchingSessionIds(query: string, limit?: number): string[];
31
30
  }
@@ -55,6 +55,7 @@ export declare const INTERNAL_DETAILS_FIELDS: readonly ["__pendingDisplayTag"];
55
55
  * (null/non-object, or no listed fields present) so callers don't pay a
56
56
  * clone cost on the common path. */
57
57
  export declare function stripInternalDetailsFields<T>(details: T | undefined): T | undefined;
58
+ export declare function wrapSteeringForModel(messages: AgentMessage[]): AgentMessage[];
58
59
  /**
59
60
  * Strip image content blocks from `message` in place. Returns the count of
60
61
  * images removed across `content` (every role that carries `ImageContent`) and
@@ -169,6 +169,7 @@ export interface SessionContext {
169
169
  /** Mode-specific data from the last mode_change entry */
170
170
  modeData?: Record<string, unknown>;
171
171
  }
172
+ export declare const EPHEMERAL_MODEL_CHANGE_ROLE = "fallback";
172
173
  /** Lists session model strings to try when restoring, in fallback order. */
173
174
  export declare function getRestorableSessionModels(models: Readonly<Record<string, string>>, lastModelChangeRole: string | undefined): string[];
174
175
  /**
@@ -27,7 +27,11 @@ export interface ParsedSlashCommand {
27
27
  /**
28
28
  * Result returned by a slash-command handler.
29
29
  *
30
- * - `void` / `undefined` — command was handled and consumed; no further input.
30
+ * - `undefined` (and the implicit `void` return) — command was handled and
31
+ * consumed; no further input. Handlers may simply omit a `return` rather than
32
+ * building `{ consumed: true }`; `void` is accepted in the handler signatures
33
+ * below so the contract typechecks under TypeScript 5.x (which does not
34
+ * coerce `() => void` to `() => T | undefined`) as well as 6.x / tsgo.
31
35
  * - `{ consumed: true }` — explicit equivalent of the above (ACP shape).
32
36
  * - `{ prompt: string }` — command handled, pass `prompt` through as the new
33
37
  * user input (e.g. `/force <tool> <prompt>` keeps `<prompt>` as the message).
@@ -97,14 +101,23 @@ export interface SlashCommandSpec extends BuiltinSlashCommand {
97
101
  /**
98
102
  * Text/ACP-mode handler. The same body is invoked from the ACP dispatcher
99
103
  * and, via the TUI adapter, when no `handleTui` override is provided.
104
+ *
105
+ * Expressed as a union of two function types — one returning a
106
+ * `SlashCommandResult`, one returning `void` — so handlers that simply
107
+ * `return` (or omit the return) typecheck under TypeScript 5.x as well as
108
+ * 6.x / tsgo. TS 5.x does not coerce a `() => void` value into a
109
+ * `() => T | undefined` slot, so the two return shapes must be siblings
110
+ * rather than a `T | void` union inside one function type (which Biome's
111
+ * `noConfusingVoidType` also rejects).
100
112
  */
101
- handle?: (command: ParsedSlashCommand, runtime: SlashCommandRuntime) => Promise<SlashCommandResult> | SlashCommandResult;
113
+ handle?: ((command: ParsedSlashCommand, runtime: SlashCommandRuntime) => SlashCommandResult | Promise<SlashCommandResult>) | ((command: ParsedSlashCommand, runtime: SlashCommandRuntime) => void | Promise<void>);
102
114
  /**
103
115
  * TUI-only handler that supersedes `handle` when both are present. Use for
104
116
  * selectors, wizards, dashboards, and anything else that requires
105
- * `InteractiveModeContext`.
117
+ * `InteractiveModeContext`. See `handle` for the rationale behind the
118
+ * function-type union shape.
106
119
  */
107
- handleTui?: (command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime) => Promise<SlashCommandResult> | SlashCommandResult;
120
+ handleTui?: ((command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime) => SlashCommandResult | Promise<SlashCommandResult>) | ((command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime) => void | Promise<void>);
108
121
  }
109
122
  /** Result returned by `executeAcpBuiltinSlashCommand`. */
110
123
  export type AcpBuiltinSlashCommandResult = false | {
@@ -16,4 +16,21 @@ export declare function stripCodeBlocks(message: string): string;
16
16
  /** Prepare a raw user message for titling: drop code blocks, then bound length. */
17
17
  export declare function prepareTitleInput(message: string): string;
18
18
  export declare function formatTitleUserMessage(message: string): string;
19
+ /**
20
+ * True when a first user message is too low-signal to title (greeting, ack,
21
+ * bare number, or empty once code/punctuation/emoji are stripped).
22
+ *
23
+ * Deterministic pre-filter: the default tiny title model (~350M local) cannot
24
+ * reliably follow a "respond with none" instruction and tends to hallucinate a
25
+ * title for trivial input, so we never ask it — the caller defers titling to
26
+ * the next message instead.
27
+ */
28
+ export declare function isLowSignalTitleInput(message: string): boolean;
29
+ /**
30
+ * Sentinel a capable title model may emit when a message carries no concrete
31
+ * task. Treated as "no title yet" so the caller can defer titling. Backstop for
32
+ * the deterministic {@link isLowSignalTitleInput} filter; kept in sync with the
33
+ * `none` instruction in `prompts/system/title-system.md`.
34
+ */
35
+ export declare const NO_TITLE_SENTINEL = "none";
19
36
  export declare function normalizeGeneratedTitle(value: string | null | undefined): string | null;
@@ -58,8 +58,22 @@ export declare abstract class SearchProvider {
58
58
  * Indicates whether this provider has the credentials/config it needs to
59
59
  * service a request right now. Implementations consult the passed
60
60
  * {@link AuthStorage} — never a sibling store.
61
+ *
62
+ * Drives auto-chain admission: providers that return `false` are skipped
63
+ * when {@link resolveProviderChain} walks the order. Explicit selection
64
+ * uses {@link isExplicitlyAvailable} instead.
61
65
  */
62
66
  abstract isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean;
67
+ /**
68
+ * Returns `true` when this provider should run when the user explicitly
69
+ * selects it, even if {@link isAvailable} would reject it for the auto
70
+ * chain. Providers that ship an unauthenticated fallback (e.g. Exa's
71
+ * public MCP) override this so explicit selection still routes through
72
+ * the fallback rather than silently falling back to another provider.
73
+ *
74
+ * Defaults to mirroring {@link isAvailable}.
75
+ */
76
+ isExplicitlyAvailable(authStorage: AuthStorage): Promise<boolean> | boolean;
63
77
  /**
64
78
  * Execute a search. Credentials MUST be resolved through `params.authStorage`.
65
79
  */
@@ -50,9 +50,18 @@ export declare function buildExaRequestBody(params: ExaSearchParams): Record<str
50
50
  export declare function searchExa(params: ExaSearchParams): Promise<SearchResponse>;
51
51
  /** Search provider for Exa. */
52
52
  export declare class ExaProvider extends SearchProvider {
53
+ #private;
53
54
  readonly id = "exa";
54
55
  readonly label = "Exa";
55
56
  isAvailable(authStorage: AuthStorage): boolean;
57
+ /**
58
+ * Exa ships an unauthenticated public MCP fallback, so an explicit
59
+ * selection (programmatic or via `providers.webSearch: exa`) routes
60
+ * through MCP even when no credential is configured. The auto chain
61
+ * still uses {@link isAvailable} so an unrelated configured provider
62
+ * keeps priority over the public fallback.
63
+ */
64
+ isExplicitlyAvailable(_authStorage: AuthStorage): boolean;
56
65
  search(params: SearchParams): Promise<SearchResponse>;
57
66
  }
58
67
  export {};
@@ -16,11 +16,11 @@ export interface PerplexitySearchParams {
16
16
  system_prompt?: string;
17
17
  search_recency_filter?: "hour" | "day" | "week" | "month" | "year";
18
18
  num_results?: number;
19
- /** Maximum output tokens. Defaults to 4096. */
19
+ /** Maximum output tokens. Defaults to 8192. */
20
20
  max_tokens?: number;
21
21
  /** Sampling temperature (0–1). Lower = more focused/factual. Defaults to 0.2. */
22
22
  temperature?: number;
23
- /** Number of search results to retrieve. Defaults to 10. */
23
+ /** Number of search results to retrieve. Defaults to 20. */
24
24
  num_search_results?: number;
25
25
  authStorage: AuthStorage;
26
26
  sessionId?: string;
@@ -50,7 +50,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
50
50
  }, {
51
51
  readonly value: "exa";
52
52
  readonly label: "Exa";
53
- readonly description: "Requires EXA_API_KEY";
53
+ readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
54
54
  }, {
55
55
  readonly value: "parallel";
56
56
  readonly label: "Parallel";
@@ -403,6 +403,7 @@ export interface PerplexityResponse {
403
403
  choices: PerplexityChoice[];
404
404
  citations?: string[] | null;
405
405
  search_results?: PerplexitySearchResult[] | null;
406
+ related_questions?: string[] | null;
406
407
  type?: PerplexityCompletionResponseType | null;
407
408
  status?: PerplexityCompletionResponseStatus | null;
408
409
  }