@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.6

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 (165) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/types/capability/rule-buckets.d.ts +1 -1
  3. package/dist/types/capability/rule.d.ts +6 -1
  4. package/dist/types/cli/update-cli.d.ts +11 -1
  5. package/dist/types/config/model-registry.d.ts +18 -1
  6. package/dist/types/discovery/at-imports.d.ts +15 -0
  7. package/dist/types/edit/diff.d.ts +3 -2
  8. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  9. package/dist/types/eval/backend.d.ts +7 -0
  10. package/dist/types/eval/js/context-manager.d.ts +1 -0
  11. package/dist/types/eval/js/executor.d.ts +2 -0
  12. package/dist/types/eval/js/index.d.ts +1 -1
  13. package/dist/types/eval/js/shared/helpers.d.ts +6 -0
  14. package/dist/types/eval/js/shared/runtime.d.ts +5 -0
  15. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  16. package/dist/types/eval/py/executor.d.ts +7 -0
  17. package/dist/types/eval/py/index.d.ts +1 -1
  18. package/dist/types/exa/index.d.ts +1 -19
  19. package/dist/types/exa/mcp-client.d.ts +10 -3
  20. package/dist/types/exa/types.d.ts +0 -83
  21. package/dist/types/export/ttsr.d.ts +14 -0
  22. package/dist/types/extensibility/extensions/types.d.ts +8 -1
  23. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
  24. package/dist/types/internal-urls/local-protocol.d.ts +10 -0
  25. package/dist/types/mcp/oauth-flow.d.ts +2 -2
  26. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  27. package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
  28. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +31 -2
  30. package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
  31. package/dist/types/modes/image-references.d.ts +8 -3
  32. package/dist/types/modes/interactive-mode.d.ts +9 -1
  33. package/dist/types/modes/theme/theme.d.ts +2 -1
  34. package/dist/types/modes/types.d.ts +3 -1
  35. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  36. package/dist/types/session/agent-session.d.ts +0 -2
  37. package/dist/types/task/render.d.ts +1 -0
  38. package/dist/types/tools/ask.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +15 -0
  40. package/dist/types/tools/index.d.ts +17 -2
  41. package/dist/types/tools/render-utils.d.ts +1 -1
  42. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  43. package/dist/types/utils/block-context.d.ts +35 -0
  44. package/dist/types/utils/git.d.ts +6 -0
  45. package/dist/types/utils/image-loading.d.ts +12 -0
  46. package/package.json +29 -9
  47. package/src/capability/rule-buckets.ts +4 -2
  48. package/src/capability/rule.ts +10 -1
  49. package/src/cli/auth-broker-cli.ts +6 -7
  50. package/src/cli/auth-gateway-cli.ts +4 -3
  51. package/src/cli/list-models.ts +5 -0
  52. package/src/cli/update-cli.ts +138 -16
  53. package/src/commit/agentic/tools/split-commit.ts +8 -1
  54. package/src/config/model-provider-priority.ts +1 -0
  55. package/src/config/model-registry.ts +81 -2
  56. package/src/debug/index.ts +4 -8
  57. package/src/discovery/at-imports.ts +273 -0
  58. package/src/discovery/builtin-rules/index.ts +4 -0
  59. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  60. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  61. package/src/discovery/helpers.ts +2 -1
  62. package/src/edit/diff.ts +114 -4
  63. package/src/edit/hashline/diff.ts +1 -1
  64. package/src/edit/hashline/execute.ts +1 -1
  65. package/src/edit/modes/patch.ts +6 -2
  66. package/src/edit/modes/replace.ts +1 -1
  67. package/src/edit/renderer.ts +12 -2
  68. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  69. package/src/eval/backend.ts +15 -0
  70. package/src/eval/js/context-manager.ts +4 -2
  71. package/src/eval/js/executor.ts +3 -0
  72. package/src/eval/js/index.ts +7 -1
  73. package/src/eval/js/shared/helpers.ts +53 -6
  74. package/src/eval/js/shared/runtime.ts +8 -0
  75. package/src/eval/js/worker-core.ts +1 -0
  76. package/src/eval/js/worker-protocol.ts +6 -0
  77. package/src/eval/py/executor.ts +12 -0
  78. package/src/eval/py/index.ts +7 -1
  79. package/src/eval/py/prelude.py +43 -4
  80. package/src/eval/py/runner.py +1 -0
  81. package/src/exa/index.ts +1 -26
  82. package/src/exa/mcp-client.ts +10 -10
  83. package/src/exa/types.ts +0 -97
  84. package/src/export/ttsr.ts +122 -1
  85. package/src/extensibility/extensions/types.ts +8 -1
  86. package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
  87. package/src/extensibility/plugins/doctor.ts +1 -1
  88. package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
  89. package/src/goals/tools/goal-tool.ts +1 -1
  90. package/src/internal-urls/docs-index.generated.ts +7 -6
  91. package/src/internal-urls/local-protocol.ts +13 -0
  92. package/src/lsp/render.ts +8 -6
  93. package/src/mcp/oauth-flow.ts +3 -3
  94. package/src/mcp/render.ts +7 -1
  95. package/src/modes/components/agent-dashboard.ts +6 -4
  96. package/src/modes/components/custom-editor.ts +12 -6
  97. package/src/modes/components/login-dialog.ts +1 -1
  98. package/src/modes/components/oauth-selector.ts +4 -4
  99. package/src/modes/components/read-tool-group.ts +10 -3
  100. package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
  101. package/src/modes/components/status-line/index.ts +1 -0
  102. package/src/modes/components/status-line/types.ts +23 -8
  103. package/src/modes/components/tool-execution.ts +1 -1
  104. package/src/modes/components/transcript-container.ts +17 -10
  105. package/src/modes/components/user-message.ts +6 -3
  106. package/src/modes/components/welcome.ts +1 -1
  107. package/src/modes/controllers/event-controller.ts +8 -0
  108. package/src/modes/controllers/extension-ui-controller.ts +143 -127
  109. package/src/modes/controllers/input-controller.ts +60 -11
  110. package/src/modes/controllers/mcp-command-controller.ts +52 -17
  111. package/src/modes/controllers/selector-controller.ts +4 -11
  112. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  113. package/src/modes/image-references.ts +13 -7
  114. package/src/modes/interactive-mode.ts +35 -3
  115. package/src/modes/rpc/rpc-mode.ts +1 -1
  116. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
  117. package/src/modes/theme/theme.ts +95 -1
  118. package/src/modes/types.ts +3 -1
  119. package/src/modes/utils/ui-helpers.ts +14 -5
  120. package/src/prompts/tools/bash.md +1 -1
  121. package/src/prompts/tools/eval.md +4 -4
  122. package/src/sdk.ts +31 -14
  123. package/src/session/agent-session.ts +290 -196
  124. package/src/session/session-manager.ts +1 -1
  125. package/src/slash-commands/builtin-registry.ts +9 -1
  126. package/src/system-prompt.ts +15 -9
  127. package/src/task/index.ts +9 -1
  128. package/src/task/render.ts +36 -14
  129. package/src/tools/ask.ts +14 -5
  130. package/src/tools/bash-interactive.ts +1 -1
  131. package/src/tools/bash.ts +14 -2
  132. package/src/tools/browser/render.ts +5 -2
  133. package/src/tools/browser/tab-worker.ts +211 -91
  134. package/src/tools/debug.ts +5 -2
  135. package/src/tools/eval-render.ts +6 -3
  136. package/src/tools/eval.ts +1 -1
  137. package/src/tools/gh-renderer.ts +29 -15
  138. package/src/tools/index.ts +32 -4
  139. package/src/tools/inspect-image-renderer.ts +12 -5
  140. package/src/tools/job.ts +9 -6
  141. package/src/tools/memory-render.ts +19 -5
  142. package/src/tools/read.ts +165 -18
  143. package/src/tools/render-utils.ts +3 -1
  144. package/src/tools/resolve.ts +1 -1
  145. package/src/tools/review.ts +1 -1
  146. package/src/tools/ssh.ts +4 -1
  147. package/src/tools/todo.ts +8 -1
  148. package/src/tools/tool-timeouts.ts +1 -1
  149. package/src/tools/write.ts +1 -1
  150. package/src/tui/code-cell.ts +1 -1
  151. package/src/utils/block-context.ts +312 -0
  152. package/src/utils/git.ts +41 -0
  153. package/src/utils/image-loading.ts +31 -1
  154. package/src/web/search/providers/codex.ts +1 -1
  155. package/src/web/search/render.ts +14 -6
  156. package/dist/types/exa/factory.d.ts +0 -13
  157. package/dist/types/exa/render.d.ts +0 -19
  158. package/dist/types/exa/researcher.d.ts +0 -9
  159. package/dist/types/exa/search.d.ts +0 -9
  160. package/dist/types/exa/websets.d.ts +0 -9
  161. package/src/exa/factory.ts +0 -60
  162. package/src/exa/render.ts +0 -244
  163. package/src/exa/researcher.ts +0 -36
  164. package/src/exa/search.ts +0 -47
  165. package/src/exa/websets.ts +0 -248
@@ -1,36 +1,6 @@
1
1
  import { type Component } from "@oh-my-pi/pi-tui";
2
- import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
3
- import type { AgentSession } from "../../session/agent-session";
4
- export interface StatusLineSegmentOptions {
5
- model?: {
6
- showThinkingLevel?: boolean;
7
- };
8
- path?: {
9
- abbreviate?: boolean;
10
- maxLength?: number;
11
- stripWorkPrefix?: boolean;
12
- };
13
- git?: {
14
- showBranch?: boolean;
15
- showStaged?: boolean;
16
- showUnstaged?: boolean;
17
- showUntracked?: boolean;
18
- };
19
- time?: {
20
- format?: "12h" | "24h";
21
- showSeconds?: boolean;
22
- };
23
- }
24
- export interface StatusLineSettings {
25
- preset?: StatusLinePreset;
26
- leftSegments?: StatusLineSegmentId[];
27
- rightSegments?: StatusLineSegmentId[];
28
- separator?: StatusLineSeparatorStyle;
29
- segmentOptions?: StatusLineSegmentOptions;
30
- showHookStatus?: boolean;
31
- sessionAccent?: boolean;
32
- }
33
- export type EffectiveStatusLineSettings = Required<Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">> & StatusLineSettings;
2
+ import type { AgentSession } from "../../../session/agent-session";
3
+ import type { EffectiveStatusLineSettings, StatusLineSettings } from "./types";
34
4
  export declare class StatusLineComponent implements Component {
35
5
  #private;
36
6
  private readonly session;
@@ -1,3 +1,4 @@
1
+ export * from "./component";
1
2
  export * from "./presets";
2
3
  export * from "./segments";
3
4
  export * from "./separators";
@@ -1,7 +1,36 @@
1
1
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../../config/settings-schema";
2
2
  import type { AgentSession } from "../../../session/agent-session";
3
- import type { StatusLineSegmentOptions, StatusLineSettings } from "../status-line";
4
- export type { StatusLinePreset, StatusLineSegmentId, StatusLineSegmentOptions, StatusLineSeparatorStyle, StatusLineSettings, };
3
+ export type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle };
4
+ export interface StatusLineSegmentOptions {
5
+ model?: {
6
+ showThinkingLevel?: boolean;
7
+ };
8
+ path?: {
9
+ abbreviate?: boolean;
10
+ maxLength?: number;
11
+ stripWorkPrefix?: boolean;
12
+ };
13
+ git?: {
14
+ showBranch?: boolean;
15
+ showStaged?: boolean;
16
+ showUnstaged?: boolean;
17
+ showUntracked?: boolean;
18
+ };
19
+ time?: {
20
+ format?: "12h" | "24h";
21
+ showSeconds?: boolean;
22
+ };
23
+ }
24
+ export interface StatusLineSettings {
25
+ preset?: StatusLinePreset;
26
+ leftSegments?: StatusLineSegmentId[];
27
+ rightSegments?: StatusLineSegmentId[];
28
+ separator?: StatusLineSeparatorStyle;
29
+ segmentOptions?: StatusLineSegmentOptions;
30
+ showHookStatus?: boolean;
31
+ sessionAccent?: boolean;
32
+ }
33
+ export type EffectiveStatusLineSettings = Required<Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">> & StatusLineSettings;
5
34
  export type RGB = readonly [number, number, number];
6
35
  export interface SegmentContext {
7
36
  session: AgentSession;
@@ -1,4 +1,12 @@
1
+ import { type Component } from "@oh-my-pi/pi-tui";
1
2
  import type { InteractiveModeContext } from "../types";
3
+ /** Renders the MCP OAuth fallback URL without hard-wrapping the copy target. */
4
+ export declare class MCPAuthorizationLinkPrompt implements Component {
5
+ #private;
6
+ constructor(url: string);
7
+ invalidate(): void;
8
+ render(_width: number): string[];
9
+ }
2
10
  export declare class MCPCommandController {
3
11
  #private;
4
12
  private ctx;
@@ -1,16 +1,21 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { type BlobPutResult } from "../session/blob-store";
3
+ /** Matches `[Image #N]`/`[Image #N, WxH]` and `[Paste #N, +X lines]`/`[Paste #N, Y chars]` tokens.
4
+ * Group 1 is the kind (`Image`/`Paste`), group 2 the 1-based index. The optional metadata
5
+ * tail (`, …`) is captured loosely (no `]`/newline) so future label tweaks keep matching. */
6
+ export declare const PLACEHOLDER_REGEX: RegExp;
3
7
  type ImageBlobWriter = (data: Buffer, options?: {
4
8
  extension?: string;
5
9
  }) => Promise<BlobPutResult>;
6
10
  type ImageBlobWriterSync = (data: Buffer, options?: {
7
11
  extension?: string;
8
12
  }) => BlobPutResult;
9
- export interface ImageReferenceRenderers {
13
+ export type PlaceholderKind = "image" | "paste";
14
+ export interface PlaceholderRenderers {
10
15
  renderText: (text: string) => string;
11
- renderReference: (label: string, index: number) => string;
16
+ renderReference: (label: string, kind: PlaceholderKind, index: number) => string;
12
17
  }
13
- export declare function renderImageReferences(text: string, renderers: ImageReferenceRenderers): string;
18
+ export declare function renderPlaceholders(text: string, renderers: PlaceholderRenderers): string;
14
19
  export declare function imageReferenceHyperlink(label: string, index: number, imageLinks: readonly (string | undefined)[] | undefined, renderLabel: (text: string) => string): string;
15
20
  export declare function materializeImageReferenceLinks(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriter): Promise<(string | undefined)[] | undefined>;
16
21
  export declare function materializeImageReferenceLinksSync(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriterSync): (string | undefined)[] | undefined;
@@ -161,6 +161,14 @@ export declare class InteractiveMode implements InteractiveModeContext {
161
161
  }): Promise<string | undefined>;
162
162
  handlePlanModeCommand(initialPrompt?: string): Promise<void>;
163
163
  handleGoalModeCommand(rest?: string): Promise<void>;
164
+ /** Manually (re-)open the plan-review overlay — bound to `/plan-review`. Lets
165
+ * the operator pull the review back up after dismissing it, or review a plan
166
+ * the agent wrote without calling `resolve`. There is no fixed plan filename:
167
+ * `getPlanReferencePath()` is empty until a plan is actually approved (and does
168
+ * not survive a restart), so this drives off the newest `local://<slug>-plan.md`
169
+ * the agent wrote — the files persist in the session artifacts dir, so the scan
170
+ * works before any review and across restarts. */
171
+ openPlanReview(): Promise<void>;
164
172
  handlePlanApproval(details: PlanApprovalDetails): Promise<void>;
165
173
  stop(): void;
166
174
  shutdown(): Promise<void>;
@@ -184,7 +192,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
184
192
  showNewVersionNotification(newVersion: string): void;
185
193
  clearEditor(): void;
186
194
  updatePendingMessagesDisplay(): void;
187
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
195
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
188
196
  flushCompactionQueue(options?: {
189
197
  willRetry?: boolean;
190
198
  }): Promise<void>;
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.editing" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.agents" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.editing" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -80,6 +80,7 @@ export declare class Theme {
80
80
  running: string;
81
81
  shadowed: string;
82
82
  aborted: string;
83
+ done: string;
83
84
  };
84
85
  get nav(): {
85
86
  cursor: string;
@@ -28,6 +28,7 @@ import type { Theme } from "./theme/theme";
28
28
  export type CompactionQueuedMessage = {
29
29
  text: string;
30
30
  mode: "steer" | "followUp";
31
+ images?: ImageContent[];
31
32
  };
32
33
  export type SubmittedUserInput = {
33
34
  text: string;
@@ -155,7 +156,7 @@ export interface InteractiveModeContext {
155
156
  showNewVersionNotification(newVersion: string): void;
156
157
  clearEditor(): void;
157
158
  updatePendingMessagesDisplay(): void;
158
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
159
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
159
160
  flushCompactionQueue(options?: {
160
161
  willRetry?: boolean;
161
162
  }): Promise<void>;
@@ -289,6 +290,7 @@ export interface InteractiveModeContext {
289
290
  disableLoopMode(): void;
290
291
  pauseLoop(): void;
291
292
  handlePlanApproval(details: PlanApprovalDetails): Promise<void>;
293
+ openPlanReview(): Promise<void>;
292
294
  initHooksAndCustomTools(): Promise<void>;
293
295
  emitCustomToolSessionEvent(reason: "start" | "switch" | "branch" | "tree" | "shutdown", previousSessionFile?: string): Promise<void>;
294
296
  setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void;
@@ -1,5 +1,5 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
- import type { AssistantMessage, Message } from "@oh-my-pi/pi-ai";
2
+ import type { AssistantMessage, ImageContent, Message } from "@oh-my-pi/pi-ai";
3
3
  import { type Component } from "@oh-my-pi/pi-tui";
4
4
  import type { InteractiveModeContext } from "../../modes/types";
5
5
  import type { SessionContext } from "../../session/session-manager";
@@ -42,7 +42,7 @@ export declare class UiHelpers {
42
42
  showWarning(warningMessage: string): void;
43
43
  showNewVersionNotification(newVersion: string): void;
44
44
  updatePendingMessagesDisplay(): void;
45
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
45
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
46
46
  isKnownSlashCommand(text: string): boolean;
47
47
  flushCompactionQueue(options?: {
48
48
  willRetry?: boolean;
@@ -299,8 +299,6 @@ export interface FreshSessionResult {
299
299
  sessionId: string;
300
300
  closedProviderSessions: number;
301
301
  }
302
- export declare const ANTHROPIC_TOOL_CALL_BATCH_CAP = 4;
303
- export declare function resolveToolCallBatchCapForModel(model: Model | undefined): number | undefined;
304
302
  export declare class AgentSession {
305
303
  #private;
306
304
  readonly agent: Agent;
@@ -19,6 +19,7 @@ export declare function renderResult(result: {
19
19
  text?: string;
20
20
  }>;
21
21
  details?: TaskToolDetails;
22
+ isError?: boolean;
22
23
  }, options: RenderResultOptions, theme: Theme, args?: TaskParams): Component;
23
24
  export declare const taskToolRenderer: {
24
25
  renderCall: typeof renderCall;
@@ -79,6 +79,7 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
79
79
  }, z.core.$strip>>;
80
80
  }, z.core.$strip>;
81
81
  readonly strict = true;
82
+ readonly concurrency = "exclusive";
82
83
  readonly loadMode = "discoverable";
83
84
  constructor(session: ToolSession);
84
85
  static createIf(session: ToolSession): AskTool | null;
@@ -11,9 +11,24 @@ declare global {
11
11
  elementFromPoint(x: number, y: number): Element | null;
12
12
  };
13
13
  }
14
+ interface ScreenshotOptions {
15
+ selector?: string;
16
+ fullPage?: boolean;
17
+ save?: string;
18
+ silent?: boolean;
19
+ }
20
+ export interface InflightOp {
21
+ label: string;
22
+ startedAt: number;
23
+ }
24
+ /** Human-readable label for a screenshot op, used in op tracking + timeout errors. */
25
+ export declare function describeScreenshot(opts?: ScreenshotOptions): string;
26
+ /** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
27
+ export declare function describeInflight(inflight: Map<number, InflightOp>): string;
14
28
  export declare class WorkerCore {
15
29
  #private;
16
30
  constructor(transport: Transport);
17
31
  nextElementId(): number;
18
32
  cacheElement(id: number, handle: ElementHandle): void;
19
33
  }
34
+ export {};
@@ -23,8 +23,6 @@ import type { WorkspaceTree } from "../workspace-tree";
23
23
  import { type CheckpointState } from "./checkpoint";
24
24
  import { type TodoPhase } from "./todo";
25
25
  export * from "../edit";
26
- export * from "../exa";
27
- export type * from "../exa/types";
28
26
  export * from "../goals";
29
27
  export * from "../lsp";
30
28
  export * from "../session/streaming-output";
@@ -287,6 +285,23 @@ export declare const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[];
287
285
  * otherwise `DEFAULT_ESSENTIAL_TOOL_NAMES`.
288
286
  */
289
287
  export declare function computeEssentialBuiltinNames(settings: Settings): string[];
288
+ /**
289
+ * Filter the initial active tool set when `tools.discoveryMode === "all"`.
290
+ *
291
+ * Non-essential discoverable built-ins are hidden — the model rediscovers them
292
+ * via `search_tool_bm25` and activates them on demand. A tool survives hiding
293
+ * when it is essential, explicitly requested, restored from a prior selection,
294
+ * or required by a forced tool_choice feature (`forceActive`). The last case is
295
+ * load-bearing: a named tool_choice (e.g. the eager `todo` prelude) must
296
+ * reference a tool present in the request, or the provider rejects it with 400.
297
+ */
298
+ export declare function filterInitialToolsForDiscoveryAll(initialToolNames: string[], opts: {
299
+ loadModeOf: (name: string) => BuiltinToolLoadMode | undefined;
300
+ essentialNames: ReadonlySet<string>;
301
+ explicitlyRequested: ReadonlySet<string>;
302
+ restored: ReadonlySet<string>;
303
+ forceActive: ReadonlySet<string>;
304
+ }): string[];
290
305
  /**
291
306
  * Public callable factory map. External callers may invoke `BUILTIN_TOOLS.read(session)` or
292
307
  * `BUILTIN_TOOLS[name](session)` to construct a tool directly.
@@ -114,7 +114,7 @@ export declare function formatErrorDetail(message: string | undefined, theme: Th
114
114
  export declare function formatEmptyMessage(message: string, theme: Theme): string;
115
115
  export type CodeFrameMarker = "" | " " | "*" | "+" | "-" | ">";
116
116
  export declare function formatCodeFrameLine(marker: CodeFrameMarker, lineNumber: string | number, content: string, lineNumberWidth: number): string;
117
- export type ToolUIStatus = "success" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
117
+ export type ToolUIStatus = "success" | "done" | "error" | "warning" | "info" | "pending" | "running" | "aborted";
118
118
  export type ToolUIColor = "success" | "error" | "warning" | "accent" | "muted";
119
119
  export interface ToolUITitleOptions {
120
120
  bold?: boolean;
@@ -15,7 +15,7 @@ export declare const TOOL_TIMEOUTS: {
15
15
  readonly eval: {
16
16
  readonly default: 30;
17
17
  readonly min: 1;
18
- readonly max: 600;
18
+ readonly max: 3600;
19
19
  };
20
20
  readonly browser: {
21
21
  readonly default: 30;
@@ -0,0 +1,35 @@
1
+ export interface LineSpan {
2
+ startLine: number;
3
+ endLine: number;
4
+ }
5
+ /** Where the source came from, so tree-sitter can pick a grammar. */
6
+ export interface BlockContextSource {
7
+ path?: string;
8
+ lang?: string;
9
+ }
10
+ export type LineEntry = {
11
+ kind: "line";
12
+ lineNumber: number;
13
+ text: string;
14
+ context: boolean;
15
+ } | {
16
+ kind: "ellipsis";
17
+ };
18
+ /**
19
+ * Resolve the off-window boundary lines for a visible window: tree-sitter
20
+ * syntactic spans first (covers brace and indentation languages), falling back
21
+ * to a lexical bracket scan when the grammar is unavailable. Returns a map of
22
+ * `lineNumber → source text` for the lines to surface, never including a line
23
+ * already visible.
24
+ */
25
+ export declare function findBlockContextLines(fullLines: readonly string[], visibleInput: ReadonlySet<number> | readonly number[], source?: BlockContextSource): Map<number, string>;
26
+ /**
27
+ * Build display entries for `visibleSpans` plus any off-window block-boundary
28
+ * lines, in source order, with `{ kind: "ellipsis" }` markers inserted across
29
+ * non-contiguous gaps. `options.lineText` lets callers substitute display text
30
+ * (e.g. column-truncated lines) for a given line number.
31
+ */
32
+ export declare function buildLineEntriesWithBlockContext(fullLines: readonly string[], visibleSpans: readonly LineSpan[], source?: BlockContextSource, options?: {
33
+ lineText?: (lineNumber: number, sourceText: string, context: boolean) => string;
34
+ }): LineEntry[];
35
+ export declare function lineEntriesToPlainText(entries: readonly LineEntry[], ellipsis?: string): string;
@@ -34,6 +34,10 @@ export interface StageHunksOptions {
34
34
  readonly rawDiff?: string;
35
35
  readonly signal?: AbortSignal;
36
36
  }
37
+ export interface HunkSelectionValidationError {
38
+ readonly path: string;
39
+ readonly message: string;
40
+ }
37
41
  export interface DiffOptions {
38
42
  readonly allowFailure?: boolean;
39
43
  readonly base?: string;
@@ -128,6 +132,8 @@ interface CommandOptions {
128
132
  * module never auto-acquire — callers wrap the critical section themselves.
129
133
  */
130
134
  export declare function withRepoLock<T>(cwd: string, fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
135
+ export declare function createHunkSelectionValidator(rawDiff: string): (selections: readonly HunkSelection[]) => HunkSelectionValidationError[];
136
+ export declare function validateHunkSelections(rawDiff: string, selections: readonly HunkSelection[]): HunkSelectionValidationError[];
131
137
  declare function parseStatusPorcelain(text: string): GitStatusSummary;
132
138
  /** Run `git diff` with the given options. Returns raw diff text. */
133
139
  export declare const diff: ((cwd: string, options?: DiffOptions) => Promise<string>) & {
@@ -1,4 +1,5 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import { type ImageResizeOptions } from "./image-resize";
2
3
  export declare const MAX_IMAGE_INPUT_BYTES: number;
3
4
  export declare const SUPPORTED_INPUT_IMAGE_MIME_TYPES: Set<string>;
4
5
  export interface LoadImageInputOptions {
@@ -23,4 +24,15 @@ export declare class ImageInputTooLargeError extends Error {
23
24
  constructor(bytes: number, maxBytes: number);
24
25
  }
25
26
  export declare function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null>;
27
+ export interface NormalizeModelContextImagesOptions {
28
+ resize?: ImageResizeOptions;
29
+ }
30
+ /**
31
+ * Normalize image blocks before they enter agent/model context. This keeps
32
+ * provider request construction from having to resize an unbounded batch of
33
+ * large images on the streaming hot path. Images are processed sequentially on
34
+ * purpose: `resizeImage` may fan out multiple encoders for one image, so the
35
+ * outer image batch must stay bounded.
36
+ */
37
+ export declare function normalizeModelContextImages(images: ImageContent[] | undefined, options?: NormalizeModelContextImagesOptions): Promise<ImageContent[] | undefined>;
26
38
  export declare function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.10.4",
4
+ "version": "15.10.6",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,14 +47,14 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.10.4",
51
- "@oh-my-pi/omp-stats": "15.10.4",
52
- "@oh-my-pi/pi-agent-core": "15.10.4",
53
- "@oh-my-pi/pi-ai": "15.10.4",
54
- "@oh-my-pi/pi-mnemopi": "15.10.4",
55
- "@oh-my-pi/pi-natives": "15.10.4",
56
- "@oh-my-pi/pi-tui": "15.10.4",
57
- "@oh-my-pi/pi-utils": "15.10.4",
50
+ "@oh-my-pi/hashline": "15.10.6",
51
+ "@oh-my-pi/omp-stats": "15.10.6",
52
+ "@oh-my-pi/pi-agent-core": "15.10.6",
53
+ "@oh-my-pi/pi-ai": "15.10.6",
54
+ "@oh-my-pi/pi-mnemopi": "15.10.6",
55
+ "@oh-my-pi/pi-natives": "15.10.6",
56
+ "@oh-my-pi/pi-tui": "15.10.6",
57
+ "@oh-my-pi/pi-utils": "15.10.6",
58
58
  "@opentelemetry/api": "^1.9.1",
59
59
  "@opentelemetry/context-async-hooks": "^2.7.1",
60
60
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -134,6 +134,14 @@
134
134
  "types": "./dist/types/cli/*.d.ts",
135
135
  "import": "./src/cli/*.ts"
136
136
  },
137
+ "./cli/gallery-fixtures": {
138
+ "types": "./dist/types/cli/gallery-fixtures/index.d.ts",
139
+ "import": "./src/cli/gallery-fixtures/index.ts"
140
+ },
141
+ "./cli/gallery-fixtures/*": {
142
+ "types": "./dist/types/cli/gallery-fixtures/*.d.ts",
143
+ "import": "./src/cli/gallery-fixtures/*.ts"
144
+ },
137
145
  "./cli/commands/*": {
138
146
  "types": "./dist/types/cli/commands/*.d.ts",
139
147
  "import": "./src/cli/commands/*.ts"
@@ -442,6 +450,14 @@
442
450
  "types": "./dist/types/modes/controllers/*.d.ts",
443
451
  "import": "./src/modes/controllers/*.ts"
444
452
  },
453
+ "./modes/setup-wizard": {
454
+ "types": "./dist/types/modes/setup-wizard/index.d.ts",
455
+ "import": "./src/modes/setup-wizard/index.ts"
456
+ },
457
+ "./modes/setup-wizard/*": {
458
+ "types": "./dist/types/modes/setup-wizard/*.d.ts",
459
+ "import": "./src/modes/setup-wizard/*.ts"
460
+ },
445
461
  "./modes/rpc/*": {
446
462
  "types": "./dist/types/modes/rpc/*.d.ts",
447
463
  "import": "./src/modes/rpc/*.ts"
@@ -499,6 +515,10 @@
499
515
  "types": "./dist/types/task/*.d.ts",
500
516
  "import": "./src/task/*.ts"
501
517
  },
518
+ "./tool-discovery/*": {
519
+ "types": "./dist/types/tool-discovery/*.d.ts",
520
+ "import": "./src/tool-discovery/*.ts"
521
+ },
502
522
  "./tools": {
503
523
  "types": "./dist/types/tools/index.d.ts",
504
524
  "import": "./src/tools/index.ts"
@@ -6,7 +6,7 @@
6
6
  * manager, and splits the rest into the always-apply and rulebook buckets.
7
7
  *
8
8
  * Bucket precedence (matches docs/rulebook-matching-pipeline.md §5):
9
- * 1. TTSR — non-empty `condition` that `TtsrManager.addRule` accepts
9
+ * 1. TTSR — non-empty `condition`/`astCondition` that `TtsrManager.addRule` accepts
10
10
  * 2. always — `alwaysApply === true`
11
11
  * 3. rulebook — has a `description`
12
12
  */
@@ -49,7 +49,9 @@ export function bucketRules(
49
49
  if (disabled.has(rule.name)) continue;
50
50
  if (!includeBuiltin && rule._source?.provider === BUILTIN_DEFAULTS_PROVIDER_ID) continue;
51
51
 
52
- const isTtsrRule = rule.condition && rule.condition.length > 0 ? ttsrManager.addRule(rule) : false;
52
+ const hasTtsrCondition =
53
+ (rule.condition && rule.condition.length > 0) || (rule.astCondition && rule.astCondition.length > 0);
54
+ const isTtsrRule = hasTtsrCondition ? ttsrManager.addRule(rule) : false;
53
55
  if (isTtsrRule) continue;
54
56
  if (rule.alwaysApply === true) {
55
57
  alwaysApplyRules.push(rule);
@@ -26,6 +26,8 @@ export interface RuleFrontmatter {
26
26
  alwaysApply?: boolean;
27
27
  /** New key for TTSR match conditions. */
28
28
  condition?: string | string[];
29
+ /** TTSR match condition(s) expressed as ast-grep patterns (edit/write streams only). */
30
+ astCondition?: string | string[];
29
31
  /** New key for TTSR stream scope. */
30
32
  scope?: string | string[];
31
33
  /** Per-rule TTSR interrupt mode override. */
@@ -51,6 +53,8 @@ export interface Rule {
51
53
  description?: string;
52
54
  /** Regex condition(s) that can trigger TTSR interruption. */
53
55
  condition?: string[];
56
+ /** ast-grep pattern condition(s) that can trigger TTSR interruption (edit/write streams only). */
57
+ astCondition?: string[];
54
58
  /** Optional stream scope tokens (for example: text, thinking, tool:edit(*.ts)). */
55
59
  scope?: string[];
56
60
  /** Per-rule TTSR interrupt mode override (falls back to global ttsr.interruptMode). */
@@ -188,10 +192,14 @@ function isLikelyFileGlob(value: string): boolean {
188
192
  * - legacy `ttsr_trigger` / `ttsrTrigger` are accepted as a `condition` fallback
189
193
  * - condition tokens that look like file globs become scope shorthands:
190
194
  * `*.rs` => `tool:edit(*.rs)`, `tool:write(*.rs)` and a catch-all condition `.*`
195
+ * - `astCondition` holds ast-grep patterns and is kept verbatim (no glob inference)
191
196
  */
192
- export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<Rule, "condition" | "scope"> {
197
+ export function parseRuleConditionAndScope(
198
+ frontmatter: RuleFrontmatter,
199
+ ): Pick<Rule, "condition" | "astCondition" | "scope"> {
193
200
  const rawCondition = frontmatter.condition ?? frontmatter.ttsr_trigger ?? frontmatter.ttsrTrigger;
194
201
  const parsedCondition = normalizeRuleField(rawCondition);
202
+ const astCondition = normalizeRuleField(frontmatter.astCondition);
195
203
  const parsedScope = normalizeScopeField(frontmatter.scope);
196
204
 
197
205
  const inferredScope: string[] = [];
@@ -213,6 +221,7 @@ export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<R
213
221
  const scope = [...(parsedScope ?? []), ...inferredScope];
214
222
  return {
215
223
  condition: condition.length > 0 ? Array.from(new Set(condition)) : undefined,
224
+ astCondition,
216
225
  scope: scope.length > 0 ? Array.from(new Set(scope)) : undefined,
217
226
  };
218
227
  }
@@ -30,6 +30,7 @@ import {
30
30
  type OAuthCredential,
31
31
  type OAuthProvider,
32
32
  type OAuthProviderInfo,
33
+ PROVIDER_REGISTRY,
33
34
  SqliteAuthCredentialStore,
34
35
  startAuthBroker,
35
36
  } from "@oh-my-pi/pi-ai";
@@ -75,13 +76,11 @@ const ACTIONS: readonly AuthBrokerAction[] = [
75
76
  ];
76
77
 
77
78
  /** Callback ports baked from the per-provider OAuth flow modules. */
78
- const CALLBACK_PORTS: Record<string, number> = {
79
- anthropic: 54545,
80
- "openai-codex": 1455,
81
- "google-gemini-cli": 8085,
82
- "google-antigravity": 51121,
83
- "gitlab-duo": 8080,
84
- };
79
+ const CALLBACK_PORTS: Record<string, number> = Object.fromEntries(
80
+ PROVIDER_REGISTRY.flatMap(provider =>
81
+ provider.callbackPort != null ? [[provider.id, provider.callbackPort] as [string, number]] : [],
82
+ ),
83
+ );
85
84
 
86
85
  function getTokenFilePath(): string {
87
86
  return path.join(getConfigRootDir(), "auth-broker.token");
@@ -175,8 +175,9 @@ async function runServe(flags: AuthGatewayCommandArgs["flags"]): Promise<void> {
175
175
  for (const provider of getBundledProviders()) {
176
176
  if (!providersWithCreds.has(provider)) continue;
177
177
  for (const model of getBundledModels(provider as GeneratedProvider)) {
178
- // First-write-wins so a canonical model id collisions across providers
179
- // stick to the provider listed first by getBundledProviders.
178
+ // Always set the qualified key (no collision possible)
179
+ modelById.set(`${model.provider}/${model.id}`, model);
180
+ // Bare id as fallback for legacy clients (first-write-wins)
180
181
  if (!modelById.has(model.id)) modelById.set(model.id, model);
181
182
  }
182
183
  }
@@ -357,7 +358,7 @@ export async function runAuthGatewayCommand(cmd: AuthGatewayCommandArgs): Promis
357
358
  /**
358
359
  * Providers whose chat endpoint expects a JSON-serialized credential blob
359
360
  * (`{ token, projectId, refreshToken, expiresAt, … }`) rather than the raw
360
- * access token. Mirrors `getOAuthApiKey` in `packages/ai/src/utils/oauth`.
361
+ * access token. Mirrors `getOAuthApiKey` in `packages/ai/src/registry/oauth`.
361
362
  */
362
363
  const STRUCTURED_API_KEY_PROVIDERS: ReadonlySet<string> = new Set([
363
364
  "github-copilot",
@@ -189,6 +189,11 @@ export async function runListModelsCommand(options: RunListModelsOptions): Promi
189
189
  modelRegistry.registerProvider(name, config, sourceId);
190
190
  }
191
191
  extensionsResult.runtime.pendingProviderRegistrations = [];
192
+ // Discover runtime (extension) provider catalogs now that they are registered.
193
+ // The full refresh in main.ts ran before extensions loaded, so this is the only
194
+ // point where extension-contributed dynamic providers get discovered. Cache-aware
195
+ // so it reuses the shared 24 h model cache instead of refetching every invocation.
196
+ await modelRegistry.refreshRuntimeProviders("online-if-uncached");
192
197
 
193
198
  await listModels(modelRegistry, searchPattern);
194
199
  }