@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +86 -43
  2. package/dist/cli.js +3540 -3521
  3. package/dist/types/advisor/advise-tool.d.ts +3 -0
  4. package/dist/types/advisor/runtime.d.ts +7 -1
  5. package/dist/types/async/job-manager.d.ts +2 -0
  6. package/dist/types/config/model-registry.d.ts +7 -0
  7. package/dist/types/config/model-resolver.d.ts +8 -0
  8. package/dist/types/config/models-config-schema.d.ts +10 -0
  9. package/dist/types/config/models-config.d.ts +6 -0
  10. package/dist/types/dap/client.d.ts +10 -0
  11. package/dist/types/dap/types.d.ts +6 -5
  12. package/dist/types/extensibility/extensions/runner.d.ts +4 -2
  13. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
  14. package/dist/types/launch/spawn-options.d.ts +10 -0
  15. package/dist/types/launch/spawn-options.test.d.ts +1 -0
  16. package/dist/types/modes/components/status-line/component.d.ts +2 -3
  17. package/dist/types/modes/components/status-line/types.d.ts +3 -1
  18. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  19. package/dist/types/modes/components/transcript-container.d.ts +4 -3
  20. package/dist/types/modes/components/tree-selector.d.ts +6 -2
  21. package/dist/types/modes/interactive-mode.d.ts +2 -0
  22. package/dist/types/modes/print-mode.d.ts +4 -0
  23. package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
  24. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
  25. package/dist/types/modes/types.d.ts +2 -0
  26. package/dist/types/sdk.d.ts +2 -0
  27. package/dist/types/session/agent-session.d.ts +19 -1
  28. package/dist/types/session/messages.d.ts +6 -0
  29. package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
  30. package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
  31. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  32. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  33. package/dist/types/tools/gh.d.ts +5 -3
  34. package/dist/types/tools/hub/index.d.ts +1 -1
  35. package/dist/types/tools/hub/jobs.d.ts +1 -0
  36. package/dist/types/tools/hub/types.d.ts +2 -0
  37. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  38. package/dist/types/utils/git.d.ts +33 -0
  39. package/dist/types/web/search/providers/codex.d.ts +1 -1
  40. package/package.json +12 -12
  41. package/src/advisor/__tests__/advisor.test.ts +150 -0
  42. package/src/advisor/advise-tool.ts +4 -0
  43. package/src/advisor/runtime.ts +38 -14
  44. package/src/async/job-manager.ts +3 -0
  45. package/src/cli/bench-cli.ts +8 -2
  46. package/src/cli/dry-balance-cli.ts +1 -0
  47. package/src/config/model-registry.ts +89 -8
  48. package/src/config/model-resolver.ts +78 -14
  49. package/src/config/models-config-schema.ts +1 -0
  50. package/src/config/settings.ts +3 -1
  51. package/src/dap/client.ts +168 -1
  52. package/src/dap/config.ts +51 -1
  53. package/src/dap/session.ts +575 -234
  54. package/src/dap/types.ts +6 -5
  55. package/src/discovery/agents.ts +2 -2
  56. package/src/extensibility/extensions/runner.ts +6 -4
  57. package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
  58. package/src/launch/broker.ts +34 -31
  59. package/src/launch/client.ts +7 -1
  60. package/src/launch/spawn-options.test.ts +31 -0
  61. package/src/launch/spawn-options.ts +17 -0
  62. package/src/lsp/types.ts +5 -1
  63. package/src/main.ts +17 -4
  64. package/src/mcp/transports/stdio.test.ts +9 -1
  65. package/src/modes/components/ask-dialog.ts +137 -73
  66. package/src/modes/components/status-line/component.ts +2 -2
  67. package/src/modes/components/status-line/segments.ts +20 -3
  68. package/src/modes/components/status-line/types.ts +3 -1
  69. package/src/modes/components/tool-execution.ts +5 -0
  70. package/src/modes/components/transcript-container.ts +23 -122
  71. package/src/modes/components/tree-selector.ts +10 -4
  72. package/src/modes/controllers/event-controller.ts +1 -2
  73. package/src/modes/controllers/input-controller.ts +1 -1
  74. package/src/modes/controllers/selector-controller.ts +29 -8
  75. package/src/modes/interactive-mode.ts +40 -8
  76. package/src/modes/noninteractive-dispose.test.ts +12 -1
  77. package/src/modes/print-mode.ts +21 -9
  78. package/src/modes/rpc/rpc-input.ts +38 -0
  79. package/src/modes/rpc/rpc-mode.ts +7 -2
  80. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
  81. package/src/modes/types.ts +2 -0
  82. package/src/modes/utils/ui-helpers.ts +3 -2
  83. package/src/prompts/tools/browser.md +3 -2
  84. package/src/prompts/tools/debug.md +2 -7
  85. package/src/prompts/tools/github.md +6 -1
  86. package/src/prompts/tools/hub.md +4 -2
  87. package/src/prompts/tools/task-async-contract.md +1 -0
  88. package/src/prompts/tools/task.md +9 -2
  89. package/src/sdk.ts +38 -6
  90. package/src/session/agent-session.ts +395 -162
  91. package/src/session/messages.test.ts +91 -0
  92. package/src/session/messages.ts +248 -110
  93. package/src/slash-commands/builtin-registry.ts +1 -0
  94. package/src/task/executor.ts +59 -33
  95. package/src/task/index.ts +46 -9
  96. package/src/task/worktree.ts +10 -0
  97. package/src/tools/browser/aria/aria-snapshot.ts +36 -8
  98. package/src/tools/browser/cmux/cmux-tab.ts +2 -1
  99. package/src/tools/browser/cmux/socket-client.ts +139 -3
  100. package/src/tools/browser/run-cancellation.ts +4 -0
  101. package/src/tools/browser/tab-protocol.ts +2 -0
  102. package/src/tools/browser/tab-supervisor.ts +21 -11
  103. package/src/tools/browser/tab-worker.ts +199 -33
  104. package/src/tools/debug.ts +3 -0
  105. package/src/tools/gh.ts +40 -2
  106. package/src/tools/hub/index.ts +4 -1
  107. package/src/tools/hub/jobs.ts +42 -1
  108. package/src/tools/hub/types.ts +2 -0
  109. package/src/tools/tool-timeouts.ts +1 -1
  110. package/src/utils/git.ts +237 -0
  111. package/src/web/search/index.ts +9 -5
  112. package/src/web/search/providers/codex.ts +195 -99
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Claims Bun's singleton stdin reader immediately and exposes a separately readable stream.
3
+ * RPC startup uses this before extension discovery so in-process modules cannot steal protocol input.
4
+ */
5
+ export function claimRpcInput(): ReadableStream<Uint8Array> {
6
+ const reader = Bun.stdin.stream().getReader();
7
+ let released = false;
8
+ const release = () => {
9
+ if (released) return;
10
+ released = true;
11
+ try {
12
+ reader.releaseLock();
13
+ } catch {}
14
+ };
15
+ return new ReadableStream({
16
+ async pull(controller) {
17
+ try {
18
+ const result = await reader.read();
19
+ if (result.done) {
20
+ release();
21
+ controller.close();
22
+ } else {
23
+ controller.enqueue(result.value);
24
+ }
25
+ } catch (error) {
26
+ release();
27
+ controller.error(error);
28
+ }
29
+ },
30
+ async cancel() {
31
+ try {
32
+ await reader.cancel();
33
+ } finally {
34
+ release();
35
+ }
36
+ },
37
+ });
38
+ }
@@ -34,6 +34,7 @@ import type { EventBus } from "../../utils/event-bus";
34
34
  import { initializeExtensions } from "../runtime-init";
35
35
  import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
36
36
  import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
37
+ import { claimRpcInput } from "./rpc-input";
37
38
  import { RpcSubagentRegistry, readRpcSubagentTranscript } from "./rpc-subagents";
38
39
  import type {
39
40
  RpcCommand,
@@ -607,6 +608,7 @@ export async function runRpcMode(
607
608
  session: AgentSession,
608
609
  setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
609
610
  eventBus?: EventBus,
611
+ input: ReadableStream<Uint8Array> = claimRpcInput(),
610
612
  ): Promise<never> {
611
613
  // Signal to RPC clients that the server is ready to accept commands
612
614
  // Suppress terminal notifications: they write \x07 (BEL) or OSC sequences directly to
@@ -1327,7 +1329,10 @@ export async function runRpcMode(
1327
1329
  return (await uiCtx.input(prompt.message, prompt.placeholder, { timeout: 600_000 })) ?? "";
1328
1330
  },
1329
1331
  });
1330
- await session.modelRegistry.refresh();
1332
+ // Provider-scoped online refresh so the just-persisted credential
1333
+ // re-runs discovery instead of reusing a fresh authoritative cache
1334
+ // row (#5780).
1335
+ await session.modelRegistry.refreshProvider(command.providerId, "online");
1331
1336
  return success(id, "login", { providerId: command.providerId });
1332
1337
  } catch (err: unknown) {
1333
1338
  return error(id, "login", err instanceof Error ? err.message : String(err));
@@ -1381,7 +1386,7 @@ export async function runRpcMode(
1381
1386
  // line is reported as an error frame and the loop keeps running instead of
1382
1387
  // throwing out of the generator and killing the whole process (issue #5194).
1383
1388
  const decoder = new TextDecoder();
1384
- for await (const line of readLines(Bun.stdin.stream())) {
1389
+ for await (const line of readLines(input ?? Bun.stdin.stream())) {
1385
1390
  const text = decoder.decode(line).trim();
1386
1391
  if (!text) continue;
1387
1392
  let parsed: unknown;
@@ -224,7 +224,9 @@ export class SignInTab implements SetupTab {
224
224
  onManualCodeInput: () =>
225
225
  this.#showPrompt({ message: "Paste the authorization code (or full redirect URL):" }),
226
226
  });
227
- await this.host.ctx.session.modelRegistry.refresh();
227
+ // Provider-scoped online refresh so the just-persisted credential re-runs
228
+ // discovery instead of reusing a fresh authoritative cache row (#5780).
229
+ await this.host.ctx.session.modelRegistry.refreshProvider(providerId, "online");
228
230
  if (this.#disposed) return;
229
231
  this.#statusLines = [
230
232
  theme.fg("success", `${theme.status.success} Signed in to ${providerId}`),
@@ -159,6 +159,7 @@ export interface InteractiveModeContext {
159
159
  goalModeEnabled: boolean;
160
160
  goalModePaused: boolean;
161
161
  loopModeEnabled: boolean;
162
+ loopModePaused: boolean;
162
163
  loopPrompt?: string;
163
164
  loopLimit?: LoopLimitRuntime;
164
165
  planModePlanFilePath?: string;
@@ -413,6 +414,7 @@ export interface InteractiveModeContext {
413
414
  handleGoalModeCommand(rest?: string): Promise<void>;
414
415
  handleGuidedGoalCommand(rest?: string): Promise<void>;
415
416
  handleLoopCommand(args?: string): Promise<string | undefined>;
417
+ setLoopPrompt(prompt: string): void;
416
418
  disableLoopMode(): void;
417
419
  pauseLoop(): void;
418
420
  handlePlanApproval(details: PlanApprovalDetails): Promise<void>;
@@ -519,10 +519,10 @@ export class UiHelpers {
519
519
  const images: ImageContent[] = message.content.filter(
520
520
  (content): content is ImageContent => content.type === "image",
521
521
  );
522
- if (images.length > 0 && assistantComponent && settings.get("terminal.showImages")) {
522
+ if (images.length > 0 && assistantComponent) {
523
523
  assistantComponent.setToolResultImages(message.toolCallId, images);
524
524
  const hasText = message.content.some(c => c.type === "text");
525
- if (!hasText) {
525
+ if (!hasText && settings.get("terminal.showImages")) {
526
526
  readToolCallArgs.delete(message.toolCallId);
527
527
  readToolCallAssistantComponents.delete(message.toolCallId);
528
528
  continue;
@@ -746,6 +746,7 @@ export class UiHelpers {
746
746
  const hintText = theme.fg("dim", ` ${theme.tree.hook} ${dequeueKey} to edit`);
747
747
  this.ctx.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
748
748
  }
749
+ this.ctx.ui.requestComponentRender(this.ctx.pendingMessagesContainer);
749
750
  }
750
751
 
751
752
  queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void {
@@ -6,7 +6,7 @@ Drives real Chromium tab; full puppeteer access via JS.
6
6
  - `run` scope: `page`, `browser`, `tab`, `display`, `assert`, `wait` available. `wait(fn)` polls until truthy — use instead of polling inside `tab.evaluate`.
7
7
 
8
8
  - `tab` helpers (drop to raw puppeteer `page` for anything uncovered):
9
- Element handles: `tab.ref("e5")` / `tab.id(n)`. Also `aria-ref=e5` inline.
9
+ Element handles: `tab.ref("e5")` / `tab.id(n)` return a handle you call methods on directly — `(await tab.id(n)).click()`. Handles are NOT selectors: `tab.click`/`type`/`fill`/`waitFor*` take STRING selectors only. Snapshot refs work in any selector slot: `tab.click("e5")` ≡ `tab.click("aria-ref=e5")`.
10
10
  Simple: `tab.goto`, `tab.click`, `tab.type`, `tab.fill`, `tab.press`, `tab.scroll`, `tab.scrollIntoView`, `tab.drag`, `tab.uploadFile`, `tab.select`, `tab.screenshot`, `tab.extract`, `tab.evaluate`.
11
11
  Waits: `tab.waitFor`, `tab.waitForSelector`, `tab.waitForUrl`, `tab.waitForResponse`, `tab.waitForNavigation`.
12
12
  Snapshots: `tab.observe()` → accessibility tree; `tab.ariaSnapshot()` → ARIA YAML with `[ref=eN]`.
@@ -14,8 +14,9 @@ Drives real Chromium tab; full puppeteer access via JS.
14
14
  Gotchas:
15
15
  - `tab.fill` NEVER works for `<select>` — use `tab.select`.
16
16
  - `tab.waitForNavigation` must start BEFORE the trigger click.
17
- - Navigation invalidates element ids — re-observe.
17
+ - Navigation and re-renders (virtualized lists, SPA updates) invalidate ids/refs — re-observe or re-snapshot, then act in the same cell.
18
18
  - Stalled actions fail fast with named error, never whole-cell timeout.
19
+ - Raw request interception is run-scoped: run end removes `request` handlers, disables interception, releases held requests.
19
20
 
20
21
  - `app.path` → NEVER tamper with a real desktop app (no stealth patches).
21
22
  - Selectors: CSS + puppeteer `aria/…`, `text/…`, `xpath/…`, `pierce/…`. Playwright-only pseudos (`:has-text()`, `:visible`) are REJECTED.
@@ -1,8 +1,3 @@
1
1
  Debugger access. Prefer over bash for program state, breakpoints, stepping, or thread inspection.
2
-
3
- Only one active session at a time. `program` is a target path, not a shell command. Directories need a directory-capable adapter (`dlv`).
4
-
5
- Adapters:
6
- - Python: `debugpy` (`pip install debugpy`)
7
- - Go: Delve (`go install github.com/go-delve/delve/cmd/dlv@latest`)
8
- - Ruby: `rdbg` (`gem install debug`)
2
+ Only one active session at a time. `program` is a target path, not a shell command.
3
+ Directories need a directory-capable adapter (e.g. `dlv`).
@@ -1,8 +1,9 @@
1
- Op-based `gh` wrapper: repos, PRs, search, checkout, push, Actions watch. Read an issue/PR via `issue://<N>`/`pr://<N>`. PR diffs: `pr://<N>/diff` (file listing), `pr://<N>/diff/<i>` (file slice, 1-indexed), `pr://<N>/diff/all` (full diff).
1
+ Op-based `gh` wrapper: repos, repository files, PRs, search, checkout, push, Actions watch. Read an issue/PR via `issue://<N>`/`pr://<N>`. PR diffs: `pr://<N>/diff` (file listing), `pr://<N>/diff/<i>` (file slice, 1-indexed), `pr://<N>/diff/all` (full diff).
2
2
 
3
3
  <instruction>
4
4
  Pick op via `op`. Beyond the field descriptions, per op:
5
5
  - `repo_view` — omit `repo` to view the current checkout.
6
+ - `file_read` — reads `path` from `repo`; omit `repo` for the current checkout and `branch` for its default branch.
6
7
  - `pr_create` — `head` defaults to the current branch.
7
8
  - `pr_checkout` — checks PR(s) out into dedicated git worktrees, not your working tree; pass an array of `pr` to batch multiple in one call.
8
9
  - `pr_push` — requires the branch to have been checked out first via `op: pr_checkout`.
@@ -15,3 +16,7 @@ Pick op via `op`. Beyond the field descriptions, per op:
15
16
  <output>
16
17
  Concise summary per op. `run_watch` failures save full logs to a session artifact.
17
18
  </output>
19
+
20
+ <critical>
21
+ GitHub-hosted repository file? MUST use `file_read`; NEVER `curl`/`wget`.
22
+ </critical>
@@ -3,7 +3,7 @@ Use `op: "list"` to discover peers. Address peers by exact roster ID — NEVER i
3
3
 
4
4
  # Messaging & Jobs
5
5
 
6
- Background jobs deliver their results automatically the moment they finish. You NEVER need to poll for output intervene only to block, kill, or inspect.
6
+ Background jobs auto-deliver when they finish. You NEVER need to poll; if `jobs`/`wait` observes a settled job first, that snapshot is the delivery and suppresses duplicate `async-result`.
7
7
 
8
8
  - **`send`** (with `to`): fire-and-forget, NEVER blocks. Delivery receipts (`delivered`/`failed`) immediate; `failed` → peer gone, don't retry.
9
9
  Sending wakes `idle`/`parked` peers. Answering: lead with answer, NEVER quote, set `replyTo`.
@@ -12,7 +12,9 @@ Background jobs deliver their results automatically the moment they finish. You
12
12
  - Bare `wait` watches every running job AND incoming messages. NEVER pass an array of every running ID; `ids` narrows to specific jobs, `from` to one peer (or use `await: true` on send).
13
13
  - **`inbox`**: drain queued messages without blocking.
14
14
  - **`cancel`**: kill background jobs by `ids` when they have hung, stalled, or are no longer needed. Returns immediately.
15
- - **`jobs`**: status snapshot of every job without waiting. Also names running subagents with no job entry — coordinate with those via `send`.
15
+ - **`jobs`**: status snapshot of every job without waiting. A settled row consumes auto-delivery. Also names running subagents with no job entry — coordinate with those via `send`.
16
+ - Job rows are process-local and expire roughly five minutes after settlement. Afterward, use the agent ID with `send`, `agent://<id>`, or `history://<id>`.
17
+ - `completed` means successful yield/job exit, not artifact acceptance. Verify claimed changes.
16
18
  - NEVER use shell tools, grep, or read other sessions' files to figure out what a peer is doing. Message them directly.
17
19
  - NEVER use hub messaging for something a tool can answer (e.g., grepping codebase, running a build).
18
20
 
@@ -0,0 +1 @@
1
+ No polling is needed. Inspecting a settled job with `hub jobs` or `hub wait` makes that snapshot its delivery, so no duplicate `async-result` follows. Job IDs live in process memory for roughly five minutes after settlement; afterward, use the agent ID with `hub send`, `agent://<id>`, or `history://<id>`. `completed` means the subagent yielded successfully, not that claimed artifacts were verified.
@@ -1,7 +1,14 @@
1
1
  {{#if asyncEnabled}}{{#if batchEnabled}}Delegate work to background subagents by passing multiple items in a single `tasks[]` batch.
2
- Execution does not block — you receive IDs immediately; results deliver when subagents finish.{{else}}Delegate work to ONE background subagent per call.
3
- Execution does not block — you receive an ID immediately; the result delivers when the subagent finishes.{{/if}}{{#if hasBlockingAgents}}
2
+ Execution does not block — you receive IDs immediately.{{else}}Delegate work to ONE background subagent per call.
3
+ Execution does not block — you receive an ID immediately.{{/if}}{{#if hasBlockingAgents}}
4
4
  Agents marked BLOCKING run inline — results return in this call; non-blocking items in the same batch still spawn as background jobs.{{/if}}{{else}}{{#if batchEnabled}}Run subagents synchronously by passing items in a `tasks[]` batch. Execution blocks until all work finishes.{{else}}Run ONE subagent synchronously. Execution blocks until work finishes.{{/if}}{{/if}}
5
+ {{#if asyncEnabled}}
6
+
7
+ # Async Job Contract
8
+ - Results auto-deliver. A settled `hub jobs`/`hub wait` snapshot is the delivery; no duplicate `async-result` follows.
9
+ - Job IDs are process-local and expire roughly five minutes after settlement. Afterward, use the agent ID with `hub send`, `agent://<id>`, or `history://<id>`.
10
+ - `completed` means successful yield/job exit, not artifact acceptance. Verify claimed changes.
11
+ {{/if}}
5
12
 
6
13
  # Task Design
7
14
  - **Agent typing:** Pick each item's `agent` type. Read-only research MUST use `agent: "scout"` (faster model). Use default worker only when no specialist fits.
package/src/sdk.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  parseModelString,
51
51
  pickDefaultAvailableModel,
52
52
  resolveAllowedModels,
53
+ resolveCliModel,
53
54
  resolveConfiguredModelPatterns,
54
55
  resolveModelRoleValue,
55
56
  } from "./config/model-resolver";
@@ -389,6 +390,8 @@ export interface CreateAgentSessionOptions {
389
390
  modelPatternAuthFallback?: string;
390
391
  /** Role name used to install retry fallbacks after deferred subagent patterns resolve. */
391
392
  modelPatternFallbackRole?: string;
393
+ /** Validated default retry chain to install when a deferred singleton pattern resolves. */
394
+ modelPatternDefaultFallbackChain?: string[];
392
395
  /** Thinking selector. Default: from settings, else unset */
393
396
  thinkingLevel?: ConfiguredThinkingLevel;
394
397
  /** Models available for cycling (Ctrl+P in interactive mode) */
@@ -2066,13 +2069,35 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2066
2069
  }
2067
2070
  }
2068
2071
  // Resolve deferred --model/subagent patterns now that extension models are
2069
- // registered. Expand role aliases (`@smol`) and comma chains to concrete
2070
- // selectors first so deferred resolution accepts everything the immediate
2071
- // path (resolveModelOverride → resolveModelRoleValue) accepts.
2072
+ // registered. Use the same CLI resolver as the immediate path so bare role
2073
+ // names, exact model names, and provider selectors keep one precedence rule.
2072
2074
  if (!model && deferredModelPatterns.length > 0) {
2073
- const expandedModelPatterns = resolveConfiguredModelPatterns(deferredModelPatterns, settings);
2074
2075
  const availableModels = modelRegistry.getAll();
2075
2076
  const matchPreferences = getModelMatchPreferences(settings);
2077
+ const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
2078
+ pattern.split(",").flatMap(selector => {
2079
+ const trimmedSelector = selector.trim();
2080
+ if (!trimmedSelector) return [];
2081
+ const resolved = resolveCliModel({
2082
+ cliModel: trimmedSelector,
2083
+ modelRegistry,
2084
+ settings,
2085
+ preferences: matchPreferences,
2086
+ });
2087
+ if (resolved.configuredPatterns && resolved.configuredPatterns.length > 0) {
2088
+ return resolved.configuredPatterns;
2089
+ }
2090
+ if (resolved.model) {
2091
+ return [
2092
+ formatModelSelectorValue(
2093
+ resolved.selector ?? formatModelStringWithRouting(resolved.model),
2094
+ resolved.thinkingLevel,
2095
+ ),
2096
+ ];
2097
+ }
2098
+ return resolveConfiguredModelPatterns([trimmedSelector], settings);
2099
+ }),
2100
+ );
2076
2101
  for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
2077
2102
  const pattern = expandedModelPatterns[patternIndex];
2078
2103
  const primary = parseModelPattern(pattern, availableModels, matchPreferences);
@@ -2118,6 +2143,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2118
2143
  seenSelectors.add(fallbackSelector);
2119
2144
  fallbackSelectors.push(fallbackSelector);
2120
2145
  }
2146
+ if (fallbackSelectors.length === 0) {
2147
+ for (const selector of options.modelPatternDefaultFallbackChain ?? []) {
2148
+ if (typeof selector !== "string" || seenSelectors.has(selector)) continue;
2149
+ seenSelectors.add(selector);
2150
+ fallbackSelectors.push(selector);
2151
+ }
2152
+ }
2121
2153
  if (fallbackSelectors.length > 0) {
2122
2154
  const modelRoles: Record<string, string> = {};
2123
2155
  const existingRoles = settings.getModelRoles();
@@ -2698,8 +2730,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2698
2730
  if (snapcompactInline) transformed = await snapcompactInline.transform(transformed, transformModel);
2699
2731
  return clampProviderContextImages(transformed, transformModel);
2700
2732
  };
2701
- const onPayload = async (payload: unknown, _model?: Model) => {
2702
- return await extensionRunner.emitBeforeProviderRequest(payload);
2733
+ const onPayload = async (payload: unknown, model?: Model) => {
2734
+ return await extensionRunner.emitBeforeProviderRequest(payload, model);
2703
2735
  };
2704
2736
  const onResponse: SimpleStreamOptions["onResponse"] = async (response, model) => {
2705
2737
  await extensionRunner.emitAfterProviderResponse(response, model);