@oh-my-pi/pi-coding-agent 15.5.6 → 15.5.8

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 (76) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/types/cli/auth-gateway-cli.d.ts +8 -0
  3. package/dist/types/commands/auth-gateway.d.ts +3 -0
  4. package/dist/types/config/settings-schema.d.ts +60 -12
  5. package/dist/types/edit/file-snapshot-store.d.ts +9 -6
  6. package/dist/types/edit/hashline/diff.d.ts +4 -5
  7. package/dist/types/edit/streaming.d.ts +2 -1
  8. package/dist/types/eval/py/index.d.ts +1 -0
  9. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  10. package/dist/types/extensibility/shared-events.d.ts +1 -1
  11. package/dist/types/internal-urls/index.d.ts +1 -0
  12. package/dist/types/internal-urls/vault-protocol.d.ts +93 -0
  13. package/dist/types/lib/xai-http.d.ts +40 -0
  14. package/dist/types/mcp/transports/http.d.ts +9 -0
  15. package/dist/types/modes/components/tool-execution.d.ts +2 -1
  16. package/dist/types/session/agent-session.d.ts +4 -1
  17. package/dist/types/tools/fetch.d.ts +16 -0
  18. package/dist/types/tools/image-gen.d.ts +6 -2
  19. package/dist/types/tools/index.d.ts +1 -0
  20. package/dist/types/tools/match-line-format.d.ts +2 -2
  21. package/dist/types/tools/plan-mode-guard.d.ts +5 -6
  22. package/dist/types/tools/render-utils.d.ts +3 -1
  23. package/dist/types/tools/tts.d.ts +18 -0
  24. package/dist/types/tools/write.d.ts +2 -0
  25. package/dist/types/utils/file-mentions.d.ts +2 -0
  26. package/package.json +8 -8
  27. package/src/cli/args.ts +2 -0
  28. package/src/cli/auth-broker-cli.ts +2 -1
  29. package/src/cli/auth-gateway-cli.ts +210 -9
  30. package/src/commands/auth-gateway.ts +7 -1
  31. package/src/config/model-registry.ts +41 -9
  32. package/src/config/settings-schema.ts +55 -13
  33. package/src/edit/file-snapshot-store.ts +9 -6
  34. package/src/edit/hashline/diff.ts +26 -13
  35. package/src/edit/hashline/execute.ts +13 -9
  36. package/src/edit/renderer.ts +9 -9
  37. package/src/edit/streaming.ts +4 -6
  38. package/src/eval/py/index.ts +1 -1
  39. package/src/extensibility/custom-tools/types.ts +1 -1
  40. package/src/extensibility/shared-events.ts +1 -1
  41. package/src/internal-urls/docs-index.generated.ts +7 -7
  42. package/src/internal-urls/index.ts +1 -0
  43. package/src/internal-urls/router.ts +2 -0
  44. package/src/internal-urls/vault-protocol.ts +936 -0
  45. package/src/lib/xai-http.ts +124 -0
  46. package/src/main.ts +1 -2
  47. package/src/mcp/transports/http.ts +29 -2
  48. package/src/modes/components/tool-execution.ts +6 -4
  49. package/src/modes/controllers/event-controller.ts +10 -3
  50. package/src/modes/controllers/selector-controller.ts +7 -2
  51. package/src/modes/interactive-mode.ts +11 -3
  52. package/src/modes/utils/ui-helpers.ts +2 -1
  53. package/src/prompts/system/system-prompt.md +3 -0
  54. package/src/prompts/tools/ast-edit.md +1 -1
  55. package/src/prompts/tools/ast-grep.md +1 -1
  56. package/src/prompts/tools/read.md +3 -3
  57. package/src/prompts/tools/search.md +1 -1
  58. package/src/sdk.ts +41 -10
  59. package/src/session/agent-session.ts +112 -14
  60. package/src/system-prompt.ts +2 -0
  61. package/src/tools/ast-edit.ts +10 -7
  62. package/src/tools/ast-grep.ts +12 -11
  63. package/src/tools/eval.ts +28 -3
  64. package/src/tools/fetch.ts +52 -24
  65. package/src/tools/image-gen.ts +205 -7
  66. package/src/tools/index.ts +1 -0
  67. package/src/tools/match-line-format.ts +2 -2
  68. package/src/tools/path-utils.ts +2 -0
  69. package/src/tools/plan-mode-guard.ts +20 -7
  70. package/src/tools/read.ts +70 -55
  71. package/src/tools/render-utils.ts +15 -0
  72. package/src/tools/search.ts +14 -14
  73. package/src/tools/tts.ts +133 -0
  74. package/src/tools/write.ts +61 -6
  75. package/src/utils/file-mentions.ts +11 -5
  76. package/src/web/search/providers/codex.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,70 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.5.8] - 2026-05-28
6
+
7
+ ### Breaking Changes
8
+
9
+ - Changed hashline edit parsing to require wrapped hunk headers such as `@@ A..B @@` (including `@@ BOF @@` and `@@ EOF @@`), with empty `@@ A..B @@` blocks deleting the anchored range and legacy inline payload forms treated as malformed
10
+
11
+ ### Added
12
+
13
+ - Added `vault.enabled` setting (Tools → Obsidian Vault, default `false`) gating the `vault://` internal URL. When disabled, `VaultProtocolHandler.resolve` / `write`, `resolveVaultUrlToPath`, and `hasObsidian()` all refuse — the latter hides the `vault://` entry from the system prompt's Handlebars `{{#if hasObsidian}}` block. Tests can opt in via `vi.spyOn(vaultProtocol, "isVaultEnabled").mockReturnValue(true)`.
14
+
15
+ - Added support for `vault://` URLs in path resolution utilities, including plan mode and internal selector parsing so `read` and edit paths can target Obsidian vault files directly
16
+ - Added `vault://` internal URLs for editable Obsidian vault files, with filesystem-backed read/write/listing and CLI-backed vault index operations.
17
+ - Added strict-mode indicators to `omp auth-gateway check` output by appending `[strict]` to strict-mode text headers and adding a top-level `strict` field in `--json` output
18
+ - `omp auth-gateway check --strict` exercises each broker-supplied credential against its provider's chat-completion endpoint (cheapest bundled chat model per provider, with 15s/attempt timeout and up to 4 catalog fall-throughs on "model not found / invalid model" errors). Surfaces failures where the usage endpoint reports 200 but the chat endpoint 401s the same bearer (revoked OAuth scope, mislabeled provider row, …). Output gains a `[chat: ok|FAIL|skip]` column in text mode and a `completion` field on each credential in `--json` mode; the chat-failed count contributes to the non-zero exit code.
19
+
20
+ ### Changed
21
+
22
+ - Changed hashline apply behavior to preserve duplicated boundary and context lines in replacement and insert payloads instead of auto-absorbing or dropping them
23
+ - Updated hashline syntax: replaced `↑`/`↓` payload sigils with `^` repeat syntax and `|` literal rows for clearer edit semantics
24
+ - Changed hashline delete syntax from bare `A:` or `A-B:` to explicit `A-B:-` inline delete marker
25
+ - Modified hashline anchor syntax to require explicit range notation `A-B:` instead of shorthand `A:` for single-line operations
26
+ - Updated hashline description in settings to clarify pure insert context behavior without arrow notation
27
+
28
+ ### Removed
29
+
30
+ - Removed the `edit.hashlineAutoDropPureInsertDuplicates` setting
31
+ - Removed the `edit.hashlineAutoDropPureInsertDuplicates` setting from configuration and execution paths
32
+ - Removed the `edit.hashlineAutoDropPureInsertDuplicates` setting
33
+ - Removed the `edit.hashlineAutoDropPureInsertDuplicates` setting from configuration and execution paths
34
+
35
+ ### Fixed
36
+
37
+ - Fixed agent yielding silently on `response.incomplete` (OpenAI Responses / Codex `stopReason: "length"`). The agent now treats output-side incompletion as a recovery case: drops the truncated/reasoning-only assistant turn, attempts context promotion to a larger model, and falls back to compaction or handoff. `AutoCompactionStartEvent.reason` and the custom-tool `auto_compaction_start.trigger` discriminator gain an `"incomplete"` value. The handoff strategy is honored for `"incomplete"` (unlike `"overflow"`, where the input is broken and handoff would hit the same wall).
38
+ - Fixed `eval` tool to resize large displayed images and append dimension notes to text output
39
+ - Fixed `write` tool to strip malformed or loose hashline section headers before writing file content
40
+ - Fixed `eval` tool image rendering to resize displayed images before returning them and append image-dimension notes to text output
41
+ - Fixed `write` tool output sanitation to strip malformed or loose hashline section headers before writing file content
42
+ - Fixed `omp auth-broker serve` crashing at startup with `logger.setTransports is not a function` — switched the call site to `import { setTransports } from "@oh-my-pi/pi-utils/logger"`, bypassing the `logger` namespace re-export that some Bun versions failed to expose at runtime
43
+ - Fixed `omp auth-gateway` returning `502 upstream_error` and refusing to rotate credentials when a provider responded with a non-401 usage-limit error (Codex `usage_limit_reached`, Anthropic `usage_limit_reached`, Google `resource_exhausted`). `classifyGatewayError` now reuses `pi-ai`'s central `isUsageLimitError` heuristic and reports those failures as `429 rate_limit_error`. `streamSimple`'s pre-emit retry hook fires on usage-limit phrasing in addition to HTTP 401; the gateway's refresh callback branches on the error type and calls `AuthStorage.markUsageLimitReached(provider, sessionId, { retryAfterMs })` — temporarily blocking just the exhausted credential and surfacing the next sibling — instead of `invalidateCredentialMatching`, which would have suspect/deleted the row. The same branching is wired into the coding-agent `streamFn` callback so subscription multi-account rotation works the same on both surfaces.
44
+ - Fixed `extractRetryHint` not recognising Codex's `Try again in ~N min.` / `… hour` / `… hours` phrasing, which left the gateway and TUI without a server-suggested retry window when an upstream account hit its usage cap. The shared `try again in` pattern now accepts `min`, `minutes`, `mins`, `h`, `hr`, `hour`, `hours` units in addition to `ms` / `s` / `sec`, and tolerates a leading `~` and embedded whitespace.
45
+ - Fixed the auth-gateway threading `sessionId: undefined` into `AuthStorage.getApiKey`, which left `#sessionLastCredential` empty and made `markUsageLimitReached` a no-op for gateway-mediated requests. Both `/v1/chat/completions`-style endpoints and the `/v1/pi/stream` fast path now derive a stable `sessionId` from the client's `prompt_cache_key` (or the existing model+system+tools+first-message hash when absent) and reuse the same identity for credential-stickiness and prefix-cache routing.
46
+ - Fixed `eval` tool to resize large displayed images and append dimension notes to text output
47
+ - Fixed `write` tool to strip malformed or loose hashline section headers before writing file content
48
+ - Fixed `eval` tool image rendering to resize displayed images before returning them and append image-dimension notes to text output
49
+ - Fixed `write` tool output sanitation to strip malformed or loose hashline section headers before writing file content
50
+ - Fixed `omp auth-broker serve` crashing at startup with `logger.setTransports is not a function` — switched the call site to `import { setTransports } from "@oh-my-pi/pi-utils/logger"`, bypassing the `logger` namespace re-export that some Bun versions failed to expose at runtime
51
+ - Fixed user shortcut Python execution to namespace session IDs like eval, so both paths share one kernel
52
+
53
+ ### Security
54
+
55
+ - Secured `vault://` reads and writes by validating URL paths and blocking traversal, absolute paths, and symlink escapes outside the selected vault root
56
+
57
+ ## [15.5.7] - 2026-05-27
58
+ ### Added
59
+ - `providers.openrouterVariant` setting (Settings → Providers → "OpenRouter Routing") to default OpenRouter requests to a routing-variant suffix (`:nitro`, `:floor`, `:online`, `:exacto`). Selectors that already name a variant (e.g. `openrouter/anthropic/claude-haiku:nitro`) keep precedence.
60
+
61
+ - `generate_image` supports xAI Grok Imagine via `providers.image=xai`. Supports `grok-imagine-image` (default) and `grok-imagine-image-quality` at aspect ratios `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`. Uses the xAI Grok OAuth credential when available, otherwise `XAI_API_KEY`.
62
+ - New `tts` tool synthesises speech via xAI Grok Voice behind the disabled-by-default `tts.enabled` setting. Built-in voices `ara`, `eve` (default), `leo`, `rex`, `sal`; custom voice IDs also accepted. Output codec inferred from the `output_path` suffix (`.wav` → `wav`, else `mp3`). Up to 15,000 characters per request.
63
+
64
+ ### Fixed
65
+
66
+ - Fixed plan-mode re-entry after approval reopening a fresh `local://PLAN.md` instead of the approved titled plan artifact, which could duplicate plan content and fail approval on an existing destination.
67
+ - Fixed `read` URL reader mode aborting after a stalled Jina request instead of falling back to trafilatura/lynx/native: Jina (and Parallel extract) now have their own per-attempt sub-budget capped at 10s, the catch handler honours only real user cancellation, and the in-process native renderer is always attempted on already-loaded HTML ([#1449](https://github.com/can1357/oh-my-pi/issues/1449))
68
+
5
69
  ## [15.5.6] - 2026-05-27
6
70
  ### Added
7
71
 
@@ -57,6 +121,10 @@
57
121
 
58
122
  - Fixed `omp` startup and `/changelog` reading the host project's `CHANGELOG.md` as omp's — `getPackageDir()` no longer falls back to the user's `cwd` when no owning `package.json` is locatable, preventing spurious `lastChangelogVersion` writes ([#1423](https://github.com/can1357/oh-my-pi/issues/1423))
59
123
 
124
+ ### Fixed
125
+
126
+ - Fixed hashline session-chain replay silently overwriting in-session edits when the model re-targeted a previously rewritten line with a stale file hash; replay now refuses unless every edit's anchor line content matches between the snapshot and the current file ([#1422](https://github.com/can1357/oh-my-pi/pull/1422))
127
+
60
128
  ## [15.5.3] - 2026-05-27
61
129
  ### Breaking Changes
62
130
 
@@ -66,6 +134,10 @@
66
134
 
67
135
  - Warned when legacy inline `LINE:TEXT` lines are accepted as payload continuations only when inside a pending multi-line `A-B:` replacement
68
136
 
137
+ ### Fixed
138
+
139
+ - Fixed runtime model registry refresh and cache loading so providers with authoritative dynamic catalogs, including Synthetic, do not re-add deprecated bundled model IDs after discovery ([#1417](https://github.com/can1357/oh-my-pi/issues/1417)).
140
+
69
141
  ## [15.5.2] - 2026-05-26
70
142
  ### Breaking Changes
71
143
 
@@ -11,6 +11,14 @@ export interface AuthGatewayCommandArgs {
11
11
  * to wire token-paste plumbing into every local client.
12
12
  */
13
13
  noAuth?: boolean;
14
+ /**
15
+ * Strict mode for `check` — additionally exercise every credential
16
+ * against its provider's chat-completion endpoint. The usage probe (run
17
+ * unconditionally) can pass while the chat endpoint still 401s the same
18
+ * bearer, so strict mode is the definitive "is this credential
19
+ * actually usable" signal. Slower and consumes a tiny amount of quota.
20
+ */
21
+ strict?: boolean;
14
22
  };
15
23
  }
16
24
  declare const ACTIONS: readonly AuthGatewayAction[];
@@ -26,6 +26,9 @@ export default class AuthGateway extends Command {
26
26
  "no-auth": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
27
27
  description: string;
28
28
  };
29
+ strict: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
30
+ description: string;
31
+ };
29
32
  };
30
33
  static examples: string[];
31
34
  run(): Promise<void>;
@@ -1918,15 +1918,6 @@ export declare const SETTINGS_SCHEMA: {
1918
1918
  readonly description: "Abort streaming edit tool calls when patch preview fails";
1919
1919
  };
1920
1920
  };
1921
- readonly "edit.hashlineAutoDropPureInsertDuplicates": {
1922
- readonly type: "boolean";
1923
- readonly default: false;
1924
- readonly ui: {
1925
- readonly tab: "editing";
1926
- readonly label: "Hashline Duplicate Insert Drop";
1927
- readonly description: "Drop payload lines that duplicate adjacent file context \u2014 2+-line context echoes on `\u2191`/`\u2193` inserts, and a single boundary line at either edge of an `A-B:` replacement";
1928
- };
1929
- };
1930
1921
  readonly "edit.blockAutoGenerated": {
1931
1922
  readonly type: "boolean";
1932
1923
  readonly default: true;
@@ -1951,7 +1942,7 @@ export declare const SETTINGS_SCHEMA: {
1951
1942
  readonly ui: {
1952
1943
  readonly tab: "editing";
1953
1944
  readonly label: "Hash Lines";
1954
- readonly description: "Include file-hash headers and line numbers in read output for hashline edit mode (\u00B6PATH#hash plus LINE:content)";
1945
+ readonly description: "Include snapshot-tag headers and line numbers in read output for hashline edit mode (\u00B6PATH#tag plus LINE:content)";
1955
1946
  };
1956
1947
  };
1957
1948
  readonly "read.defaultLimit": {
@@ -2393,6 +2384,15 @@ export declare const SETTINGS_SCHEMA: {
2393
2384
  readonly description: "Enable the calculator tool for basic calculations";
2394
2385
  };
2395
2386
  };
2387
+ readonly "tts.enabled": {
2388
+ readonly type: "boolean";
2389
+ readonly default: false;
2390
+ readonly ui: {
2391
+ readonly tab: "tools";
2392
+ readonly label: "Text-to-Speech";
2393
+ readonly description: "Enable the tts tool for xAI Grok Voice speech synthesis";
2394
+ };
2395
+ };
2396
2396
  readonly "recipe.enabled": {
2397
2397
  readonly type: "boolean";
2398
2398
  readonly default: true;
@@ -2429,6 +2429,15 @@ export declare const SETTINGS_SCHEMA: {
2429
2429
  readonly description: "Allow the read tool to fetch and process URLs";
2430
2430
  };
2431
2431
  };
2432
+ readonly "vault.enabled": {
2433
+ readonly type: "boolean";
2434
+ readonly default: false;
2435
+ readonly ui: {
2436
+ readonly tab: "tools";
2437
+ readonly label: "Obsidian Vault";
2438
+ readonly description: "Enable the vault:// internal URL for reading and editing Obsidian vault content via the Obsidian CLI. When disabled, vault:// resolution is refused and the vault:// entry is omitted from the system prompt.";
2439
+ };
2440
+ };
2432
2441
  readonly "github.enabled": {
2433
2442
  readonly type: "boolean";
2434
2443
  readonly default: false;
@@ -3118,7 +3127,7 @@ export declare const SETTINGS_SCHEMA: {
3118
3127
  };
3119
3128
  readonly "providers.image": {
3120
3129
  readonly type: "enum";
3121
- readonly values: readonly ["auto", "openai", "gemini", "openrouter"];
3130
+ readonly values: readonly ["auto", "openai", "antigravity", "xai", "gemini", "openrouter"];
3122
3131
  readonly default: "auto";
3123
3132
  readonly ui: {
3124
3133
  readonly tab: "providers";
@@ -3127,11 +3136,19 @@ export declare const SETTINGS_SCHEMA: {
3127
3136
  readonly options: readonly [{
3128
3137
  readonly value: "auto";
3129
3138
  readonly label: "Auto";
3130
- readonly description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini";
3139
+ readonly description: "Priority: GPT model image tool > Antigravity > xAI > OpenRouter > Gemini";
3131
3140
  }, {
3132
3141
  readonly value: "openai";
3133
3142
  readonly label: "OpenAI";
3134
3143
  readonly description: "Uses the active GPT Responses/Codex model";
3144
+ }, {
3145
+ readonly value: "antigravity";
3146
+ readonly label: "Antigravity";
3147
+ readonly description: "Requires google-antigravity OAuth";
3148
+ }, {
3149
+ readonly value: "xai";
3150
+ readonly label: "xAI Grok Imagine";
3151
+ readonly description: "Requires xAI Grok OAuth or XAI_API_KEY";
3135
3152
  }, {
3136
3153
  readonly value: "gemini";
3137
3154
  readonly label: "Gemini";
@@ -3185,6 +3202,37 @@ export declare const SETTINGS_SCHEMA: {
3185
3202
  }];
3186
3203
  };
3187
3204
  };
3205
+ readonly "providers.openrouterVariant": {
3206
+ readonly type: "enum";
3207
+ readonly values: readonly ["default", "nitro", "floor", "online", "exacto"];
3208
+ readonly default: "default";
3209
+ readonly ui: {
3210
+ readonly tab: "providers";
3211
+ readonly label: "OpenRouter Routing";
3212
+ readonly description: "Default routing-variant suffix appended to OpenRouter model IDs (overridden when the selector already names a variant)";
3213
+ readonly options: readonly [{
3214
+ readonly value: "default";
3215
+ readonly label: "Default";
3216
+ readonly description: "No suffix; use OpenRouter's default routing";
3217
+ }, {
3218
+ readonly value: "nitro";
3219
+ readonly label: ":nitro";
3220
+ readonly description: "Prioritize throughput / lowest latency";
3221
+ }, {
3222
+ readonly value: "floor";
3223
+ readonly label: ":floor";
3224
+ readonly description: "Prioritize cheapest available provider";
3225
+ }, {
3226
+ readonly value: "online";
3227
+ readonly label: ":online";
3228
+ readonly description: "Enable OpenRouter's web-search plugin";
3229
+ }, {
3230
+ readonly value: "exacto";
3231
+ readonly label: ":exacto";
3232
+ readonly description: "Cherry-picked high-quality providers (only defined for select models)";
3233
+ }];
3234
+ };
3235
+ };
3188
3236
  readonly "providers.parallelFetch": {
3189
3237
  readonly type: "boolean";
3190
3238
  readonly default: true;
@@ -2,17 +2,20 @@
2
2
  * Session-bound file snapshot store.
3
3
  *
4
4
  * Used by `read` and `search` to record exactly what the model saw, and by
5
- * the hashline patcher to recover from stale section hashes (file changed
6
- * externally between read and edit, or a prior in-session edit advanced
7
- * the hash). The store is the {@link InMemorySnapshotStore} implementation
5
+ * the hashline patcher to verify or recover from stale section tags (file
6
+ * changed externally between read and edit, or a prior in-session edit
7
+ * advanced the tag). The store is the {@link InMemorySnapshotStore}
8
8
  * from `@oh-my-pi/hashline`; the only coding-agent-specific concern here
9
- * is wiring it onto the per-session {@link ToolSession} object.
9
+ * is wiring it onto the per-session owner object.
10
10
  */
11
11
  import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
12
- import type { ToolSession } from "../tools";
12
+ interface FileSnapshotStoreOwner {
13
+ fileSnapshotStore?: InMemorySnapshotStore;
14
+ }
13
15
  /**
14
16
  * Look up (or lazily create) the file snapshot store attached to a session.
15
17
  * Storage lives on `session.fileSnapshotStore` so it ages out exactly with
16
18
  * the session itself.
17
19
  */
18
- export declare function getFileSnapshotStore(session: ToolSession): InMemorySnapshotStore;
20
+ export declare function getFileSnapshotStore(session: FileSnapshotStoreOwner): InMemorySnapshotStore;
21
+ export {};
@@ -5,13 +5,12 @@
5
5
  * pair to {@link generateDiffString} so the renderer can show the diff
6
6
  * while the tool call is still streaming.
7
7
  *
8
- * Validation is intentionally light: only the section file hash is checked
8
+ * Validation is intentionally light: only the section snapshot tag is checked
9
9
  * (so the preview goes red when anchors are stale), no plan-mode guards
10
10
  * and no auto-generated-file refusal — those belong on the write path.
11
11
  */
12
- import { type PatchSection } from "@oh-my-pi/hashline";
12
+ import { type PatchSection, type SnapshotStore } from "@oh-my-pi/hashline";
13
13
  export interface HashlineDiffOptions {
14
- autoDropPureInsertDuplicates?: boolean;
15
14
  /**
16
15
  * Use the streaming-tolerant applier ({@link PatchSection.applyPartialTo})
17
16
  * so trailing in-flight ops do not throw or emit phantom edits. Streaming
@@ -19,7 +18,7 @@ export interface HashlineDiffOptions {
19
18
  */
20
19
  streaming?: boolean;
21
20
  }
22
- export declare function computeHashlineSectionDiff(section: PatchSection, cwd: string, options?: HashlineDiffOptions): Promise<{
21
+ export declare function computeHashlineSectionDiff(section: PatchSection, cwd: string, snapshots: SnapshotStore, options?: HashlineDiffOptions): Promise<{
23
22
  diff: string;
24
23
  firstChangedLine: number | undefined;
25
24
  } | {
@@ -27,7 +26,7 @@ export declare function computeHashlineSectionDiff(section: PatchSection, cwd: s
27
26
  }>;
28
27
  export declare function computeHashlineDiff(input: {
29
28
  input: string;
30
- }, cwd: string, options?: HashlineDiffOptions): Promise<{
29
+ }, cwd: string, snapshots: SnapshotStore, options?: HashlineDiffOptions): Promise<{
31
30
  diff: string;
32
31
  firstChangedLine: number | undefined;
33
32
  } | {
@@ -12,6 +12,7 @@
12
12
  * The shared renderer / `ToolExecutionComponent` consult the strategy via
13
13
  * the injected `editMode` rather than probing argument shape.
14
14
  */
15
+ import { type SnapshotStore } from "@oh-my-pi/hashline";
15
16
  import type { Theme } from "../modes/theme/theme";
16
17
  import { type EditMode, resolveEditMode } from "../utils/edit-mode";
17
18
  export interface PerFileDiffPreview {
@@ -23,9 +24,9 @@ export interface PerFileDiffPreview {
23
24
  export interface StreamingDiffContext {
24
25
  cwd: string;
25
26
  signal: AbortSignal;
27
+ snapshots: SnapshotStore;
26
28
  fuzzyThreshold?: number;
27
29
  allowFuzzy?: boolean;
28
- hashlineAutoDropPureInsertDuplicates?: boolean;
29
30
  /**
30
31
  * True while the tool's arguments are still streaming in. Strategies that
31
32
  * accept free-form text input (apply_patch, hashline) trim the trailing
@@ -1,5 +1,6 @@
1
1
  import type { ToolSession } from "../../tools";
2
2
  import type { ExecutorBackendExecOptions, ExecutorBackendResult } from "../backend";
3
+ export declare function namespaceSessionId(sessionId: string): string;
3
4
  declare const _default: {
4
5
  id: "python";
5
6
  label: string;
@@ -85,7 +85,7 @@ export type CustomToolSessionEvent = {
85
85
  previousSessionFile: string | undefined;
86
86
  } | {
87
87
  reason: "auto_compaction_start";
88
- trigger: "threshold" | "overflow" | "idle";
88
+ trigger: "threshold" | "overflow" | "idle" | "incomplete";
89
89
  action: "context-full" | "handoff";
90
90
  } | {
91
91
  reason: "auto_compaction_end";
@@ -159,7 +159,7 @@ export interface TurnEndEvent {
159
159
  /** Fired when auto-compaction starts */
160
160
  export interface AutoCompactionStartEvent {
161
161
  type: "auto_compaction_start";
162
- reason: "threshold" | "overflow" | "idle";
162
+ reason: "threshold" | "overflow" | "idle" | "incomplete";
163
163
  action: "context-full" | "handoff";
164
164
  }
165
165
  /** Fired when auto-compaction ends */
@@ -20,3 +20,4 @@ export * from "./router";
20
20
  export * from "./rule-protocol";
21
21
  export * from "./skill-protocol";
22
22
  export type * from "./types";
23
+ export * from "./vault-protocol";
@@ -0,0 +1,93 @@
1
+ import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, WriteContext } from "./types";
2
+ type ContentType = InternalResource["contentType"];
3
+ type VaultParamValue = string | true;
4
+ type VaultParams = Record<string, VaultParamValue>;
5
+ type FileOp = "outline" | "backlinks" | "links" | "tags" | "properties" | "tasks" | "wordcount" | "history" | "base";
6
+ type VaultOp = "search" | "daily" | "daily-path" | "tags" | "tag" | "tasks" | "orphans" | "unresolved" | "deadends" | "bases" | "bookmarks" | "recents" | "templates" | "aliases" | "properties" | "property";
7
+ export interface VaultReference {
8
+ vault: string | null;
9
+ active: boolean;
10
+ forwardVault: boolean;
11
+ display: string;
12
+ }
13
+ export type ParsedVaultUrl = {
14
+ kind: "list-vaults";
15
+ url: string;
16
+ params: VaultParams;
17
+ } | {
18
+ kind: "vault-info";
19
+ url: string;
20
+ ref: VaultReference;
21
+ params: VaultParams;
22
+ } | {
23
+ kind: "fs-dir";
24
+ url: string;
25
+ ref: VaultReference;
26
+ relativePath: string;
27
+ params: VaultParams;
28
+ } | {
29
+ kind: "fs-file";
30
+ url: string;
31
+ ref: VaultReference;
32
+ relativePath: string;
33
+ params: VaultParams;
34
+ } | {
35
+ kind: "file-op";
36
+ url: string;
37
+ ref: VaultReference;
38
+ relativePath: string;
39
+ op: FileOp;
40
+ params: VaultParams;
41
+ } | {
42
+ kind: "vault-op";
43
+ url: string;
44
+ ref: VaultReference;
45
+ op: VaultOp;
46
+ params: VaultParams;
47
+ };
48
+ export interface ObsidianSpawnResult {
49
+ stdout: string;
50
+ stderr: string;
51
+ exitCode: number;
52
+ }
53
+ export interface VaultProtocolHandlerOptions {
54
+ spawnObsidian?: typeof spawnObsidian;
55
+ resolveObsidianBinary?: () => string | null;
56
+ }
57
+ interface CliInvocation {
58
+ args: string[];
59
+ contentType: ContentType;
60
+ opLabel: string;
61
+ }
62
+ export declare function parseVaultUrl(input: string | InternalUrl): ParsedVaultUrl;
63
+ export declare function spawnObsidian(bin: string, args: string[], signal?: AbortSignal, timeoutMs?: number): Promise<ObsidianSpawnResult>;
64
+ export declare function resolveObsidianBinary(): string | null;
65
+ /**
66
+ * Whether the `vault://` protocol is enabled in the active settings profile.
67
+ *
68
+ * Reads `vault.enabled` from the global settings singleton. Falls back to the
69
+ * schema default when settings are not yet initialized (e.g. during isolated
70
+ * unit tests that exercise the handler before the host calls `Settings.init`).
71
+ */
72
+ export declare function isVaultEnabled(): boolean;
73
+ export declare function hasObsidian(): boolean;
74
+ export declare class VaultDisabledError extends Error {
75
+ constructor();
76
+ }
77
+ export declare function resolveVaultUrlToPath(input: string | InternalUrl): string;
78
+ export declare function buildObsidianCliInvocation(parsed: Extract<ParsedVaultUrl, {
79
+ kind: "file-op" | "vault-op";
80
+ }>): CliInvocation;
81
+ export declare class VaultProtocolHandler implements ProtocolHandler {
82
+ #private;
83
+ readonly scheme = "vault";
84
+ readonly immutable = false;
85
+ constructor(options?: VaultProtocolHandlerOptions);
86
+ static resetForTests(): void;
87
+ static setObsidianBinaryForTests(value: string | null | undefined): void;
88
+ static setVaultDirectoryForTests(entries: ReadonlyMap<string, string> | Record<string, string> | undefined): void;
89
+ static setActiveVaultPathForTests(vaultPath: string | undefined): void;
90
+ resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource>;
91
+ write(url: InternalUrl, content: string, context?: WriteContext): Promise<void>;
92
+ }
93
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { ModelRegistry } from "../config/model-registry";
2
+ interface XAICredentials {
3
+ provider: "xai-oauth" | "xai";
4
+ apiKey: string;
5
+ baseURL: string;
6
+ }
7
+ export declare function ohMyPiXAIUserAgent(): string;
8
+ /**
9
+ * Resolve xAI credentials for HTTP tool calls.
10
+ *
11
+ * Credential priority:
12
+ * 1. xai-oauth — only when a *dedicated* xai-oauth source exists. Composed
13
+ * of two checks against the registry layer:
14
+ * a. `authStorage.hasNonEnvCredential("xai-oauth")` covers stored
15
+ * credentials (OAuth or api_key), runtime overrides (CLI
16
+ * `--api-key` for xai-oauth), config overrides (models.yml
17
+ * `providers.xai-oauth.apiKey`), and fallback resolvers.
18
+ * b. `$env.XAI_OAUTH_TOKEN` covers the xai-oauth-specific env var.
19
+ * `XAI_API_KEY` is intentionally NOT a signal here, even though the
20
+ * env-fallback map (`stream.ts: "xai-oauth"`) lets xai-oauth borrow it
21
+ * as a back-compat convenience: the borrow lets API-key-only setups
22
+ * satisfy the xai-oauth branch and then resolve baseUrl under
23
+ * xai-oauth instead of xai, silently bypassing `providers.xai.baseUrl`
24
+ * overrides for image/TTS traffic. The gate routes the borrow case to
25
+ * step 2 while preserving every dedicated xai-oauth path.
26
+ * 2. xai (plain API key). Delegates to ModelRegistry.getApiKeyForProvider
27
+ * which runs AuthStorage.getApiKey's full cascade: runtime override →
28
+ * models.yml config override → stored api_key credential → OAuth
29
+ * resolution → XAI_API_KEY env var → custom fallback resolver.
30
+ *
31
+ * baseURL: see `resolveXAIBaseURL` above. Resolved AFTER the credential
32
+ * decision so the scoped (provider, id) lookup is unambiguous. `modelId`
33
+ * is optional; probes / tool-availability checks pass `undefined` and fall
34
+ * through to env/default.
35
+ *
36
+ * Returns null when neither credential is available. Caller is responsible
37
+ * for surfacing an actionable error message in that case.
38
+ */
39
+ export declare function resolveXAIHttpCredentials(modelRegistry: ModelRegistry, modelId?: string): Promise<XAICredentials | null>;
40
+ export {};
@@ -1,4 +1,13 @@
1
1
  import type { MCPHttpServerConfig, MCPRequestOptions, MCPSseServerConfig, MCPTransport } from "../../mcp/types";
2
+ /**
3
+ * Best-effort startup deadline for the optional Streamable HTTP GET SSE listener.
4
+ *
5
+ * Returns `0` (disabled) when the operator has explicitly disabled MCP client-side
6
+ * timeouts via `timeout: 0` or `OMP_MCP_TIMEOUT_MS=0`, mirroring the rest of the
7
+ * MCP timeout surface. Otherwise caps the wait at one second and scales below
8
+ * short request timeouts so connect-time never exceeds the request budget.
9
+ */
10
+ export declare function resolveSSEConnectTimeoutMs(configTimeout?: number): number;
2
11
  /**
3
12
  * HTTP transport for MCP servers.
4
13
  * Uses POST for requests, supports SSE responses.
@@ -1,10 +1,11 @@
1
+ import type { SnapshotStore } from "@oh-my-pi/hashline";
1
2
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
3
  import { Container, type TUI } from "@oh-my-pi/pi-tui";
3
4
  export interface ToolExecutionOptions {
5
+ snapshots?: SnapshotStore;
4
6
  showImages?: boolean;
5
7
  editFuzzyThreshold?: number;
6
8
  editAllowFuzzy?: boolean;
7
- hashlineAutoDropPureInsertDuplicates?: boolean;
8
9
  }
9
10
  export interface ToolExecutionHandle {
10
11
  updateArgs(args: any, toolCallId?: string): void;
@@ -12,6 +12,7 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
+ import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
15
16
  import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
16
17
  import { type CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
17
18
  import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
@@ -48,7 +49,7 @@ import { YieldQueue } from "./yield-queue";
48
49
  /** Session-specific events that extend the core AgentEvent */
49
50
  export type AgentSessionEvent = AgentEvent | {
50
51
  type: "auto_compaction_start";
51
- reason: "threshold" | "overflow" | "idle";
52
+ reason: "threshold" | "overflow" | "idle" | "incomplete";
52
53
  action: "context-full" | "handoff";
53
54
  } | {
54
55
  type: "auto_compaction_end";
@@ -267,6 +268,7 @@ export declare class AgentSession {
267
268
  readonly sessionManager: SessionManager;
268
269
  readonly settings: Settings;
269
270
  readonly yieldQueue: YieldQueue;
271
+ fileSnapshotStore?: InMemorySnapshotStore;
270
272
  readonly configWarnings: string[];
271
273
  readonly rawSseDebugBuffer: RawSseDebugBuffer;
272
274
  constructor(config: AgentSessionConfig);
@@ -454,6 +456,7 @@ export declare class AgentSession {
454
456
  get goalRuntime(): GoalRuntime;
455
457
  markPlanReferenceSent(): void;
456
458
  setPlanReferencePath(path: string): void;
459
+ getPlanReferencePath(): string;
457
460
  get clientBridge(): ClientBridge | undefined;
458
461
  setClientBridge(bridge: ClientBridge | undefined): void;
459
462
  getCheckpointState(): CheckpointState | undefined;
@@ -1,8 +1,10 @@
1
1
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
2
  import { type Component } from "@oh-my-pi/pi-tui";
3
+ import type { Settings } from "../config/settings";
3
4
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
4
5
  import { type Theme } from "../modes/theme/theme";
5
6
  import type { ToolSession } from "../sdk";
7
+ import type { AgentStorage } from "../session/agent-storage";
6
8
  import { type OutputMeta } from "./output-meta";
7
9
  import { type LineRange } from "./path-utils";
8
10
  export declare function isReadableUrlPath(value: string): boolean;
@@ -15,6 +17,20 @@ export interface ParsedReadUrlTarget {
15
17
  ranges?: readonly LineRange[];
16
18
  }
17
19
  export declare function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null;
20
+ /**
21
+ * Render HTML to markdown using Parallel, jina, trafilatura, lynx, then the
22
+ * in-process native converter. The overall `timeout` budget bounds the call,
23
+ * but remote reader requests are additionally capped at `REMOTE_READER_MAX_MS`
24
+ * so that a hung remote endpoint cannot prevent local fallbacks from running.
25
+ * Only a real `userSignal` cancellation aborts the chain — remote per-attempt
26
+ * timeouts and the overall reader-mode timeout still allow later renderers
27
+ * (especially the purely-local native converter) to be tried.
28
+ */
29
+ export declare function renderHtmlToText(url: string, html: string, timeout: number, settings: Settings, userSignal: AbortSignal | undefined, storage: AgentStorage | null): Promise<{
30
+ content: string;
31
+ ok: boolean;
32
+ method: string;
33
+ }>;
18
34
  interface FetchImagePayload {
19
35
  data: string;
20
36
  mimeType: string;
@@ -2,7 +2,8 @@ import { type Model } from "@oh-my-pi/pi-ai";
2
2
  import * as z from "zod/v4";
3
3
  import { type ModelRegistry } from "../config/model-registry";
4
4
  import type { CustomTool } from "../extensibility/custom-tools/types";
5
- type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter";
5
+ export type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | "xai";
6
+ export type ImageProviderPreference = Exclude<ImageProvider, "openai-codex"> | "auto";
6
7
  declare const responseModalitySchema: z.ZodEnum<{
7
8
  IMAGE: "IMAGE";
8
9
  TEXT: "TEXT";
@@ -19,6 +20,8 @@ export declare const imageGenSchema: z.ZodObject<{
19
20
  aspect_ratio: z.ZodOptional<z.ZodEnum<{
20
21
  "16:9": "16:9";
21
22
  "1:1": "1:1";
23
+ "2:3": "2:3";
24
+ "3:2": "3:2";
22
25
  "3:4": "3:4";
23
26
  "4:3": "4:3";
24
27
  "9:16": "9:16";
@@ -70,8 +73,9 @@ interface InlineImageData {
70
73
  data: string;
71
74
  mimeType: string;
72
75
  }
76
+ export declare function isImageProviderPreference(value: unknown): value is ImageProviderPreference;
73
77
  /** Set the preferred image provider from settings */
74
- export declare function setPreferredImageProvider(provider: ImageProvider | "auto"): void;
78
+ export declare function setPreferredImageProvider(provider: ImageProviderPreference): void;
75
79
  export declare const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails>;
76
80
  export declare function getImageGenTools(modelRegistry?: ModelRegistry, activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
77
81
  export declare function getImageGenToolsWithRegistry(modelRegistry: ModelRegistry, activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
@@ -54,6 +54,7 @@ export * from "./search";
54
54
  export * from "./search-tool-bm25";
55
55
  export * from "./ssh";
56
56
  export * from "./todo-write";
57
+ export * from "./tts";
57
58
  export * from "./write";
58
59
  export * from "./yield";
59
60
  /** Tool type (AgentTool from pi-ai) */
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Matched lines are prefixed with `*`; context lines are prefixed with a single
5
5
  * space so line numbers align in column. In hashline mode the line uses the
6
- * editable `LINE:content` shape under a file-hash header; in plain mode it keeps
7
- * the legacy `LINE|content` display-only shape. Line numbers are never padded.
6
+ * editable `LINE:content` shape under a snapshot-tag header; in plain mode it
7
+ * keeps the legacy `LINE|content` display-only shape. Line numbers are never padded.
8
8
  */
9
9
  export declare function formatMatchLine(lineNumber: number, line: string, isMatch: boolean, options: {
10
10
  useHashLines: boolean;