@oh-my-pi/pi-coding-agent 16.2.11 → 16.2.13

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 (89) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/cli.js +2982 -2825
  3. package/dist/types/cli/bench-cli.d.ts +0 -5
  4. package/dist/types/cli/dry-balance-cli.d.ts +0 -5
  5. package/dist/types/cli/models-cli.d.ts +1 -1
  6. package/dist/types/config/inline-tool-descriptors-mode.d.ts +1 -2
  7. package/dist/types/config/model-registry.d.ts +1 -23
  8. package/dist/types/config/model-resolver.d.ts +10 -15
  9. package/dist/types/config/models-config-schema.d.ts +12 -6
  10. package/dist/types/config/models-config.d.ts +9 -6
  11. package/dist/types/config/settings-schema.d.ts +31 -1
  12. package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +1 -0
  13. package/dist/types/extensibility/extensions/runner.d.ts +15 -0
  14. package/dist/types/lsp/client.d.ts +9 -3
  15. package/dist/types/modes/controllers/tool-args-reveal.d.ts +0 -5
  16. package/dist/types/task/spawn-policy.d.ts +17 -0
  17. package/dist/types/task/spawn-policy.test.d.ts +1 -0
  18. package/dist/types/task/types.d.ts +8 -2
  19. package/dist/types/tools/__tests__/eval-description.test.d.ts +1 -0
  20. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +2 -2
  21. package/dist/types/tools/browser/registry.d.ts +2 -0
  22. package/dist/types/tools/browser/run-cancellation.d.ts +4 -0
  23. package/dist/types/tools/browser/tab-supervisor.d.ts +30 -1
  24. package/dist/types/tools/eval.d.ts +3 -6
  25. package/dist/types/tools/write.d.ts +1 -1
  26. package/dist/types/utils/image-vision-fallback.d.ts +1 -1
  27. package/package.json +12 -12
  28. package/src/advisor/__tests__/advisor.test.ts +1 -1
  29. package/src/auto-thinking/classifier.ts +1 -1
  30. package/src/cli/bench-cli.ts +3 -12
  31. package/src/cli/dry-balance-cli.ts +1 -6
  32. package/src/cli/models-cli.ts +3 -81
  33. package/src/commands/models.ts +1 -2
  34. package/src/commit/model-selection.ts +4 -4
  35. package/src/config/inline-tool-descriptors-mode.ts +1 -2
  36. package/src/config/model-discovery.ts +32 -6
  37. package/src/config/model-registry.ts +11 -225
  38. package/src/config/model-resolver.ts +23 -135
  39. package/src/config/models-config-schema.ts +13 -22
  40. package/src/config/prompt-templates.ts +20 -0
  41. package/src/config/settings-schema.ts +35 -1
  42. package/src/config/settings.ts +60 -24
  43. package/src/eval/__tests__/agent-bridge.test.ts +17 -3
  44. package/src/eval/agent-bridge.ts +7 -9
  45. package/src/eval/completion-bridge.ts +1 -1
  46. package/src/eval/py/__tests__/runner-shell-output.test.ts +157 -0
  47. package/src/eval/py/runner.py +169 -18
  48. package/src/extensibility/extensions/model-api.ts +1 -3
  49. package/src/extensibility/extensions/runner.ts +62 -5
  50. package/src/lsp/client.ts +162 -45
  51. package/src/lsp/index.ts +31 -21
  52. package/src/main.ts +0 -1
  53. package/src/mcp/transports/http.ts +19 -17
  54. package/src/mcp/transports/stdio.test.ts +51 -1
  55. package/src/mcp/transports/stdio.ts +19 -9
  56. package/src/memories/index.ts +0 -1
  57. package/src/mnemopi/backend.ts +1 -1
  58. package/src/modes/components/model-selector.ts +32 -186
  59. package/src/modes/controllers/event-controller.ts +8 -39
  60. package/src/modes/controllers/selector-controller.ts +0 -1
  61. package/src/modes/controllers/tool-args-reveal.ts +0 -12
  62. package/src/prompts/system/subagent-system-prompt.md +2 -2
  63. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  64. package/src/prompts/tools/eval.md +2 -2
  65. package/src/prompts/tools/ssh.md +1 -0
  66. package/src/prompts/tools/task.md +1 -1
  67. package/src/sdk.ts +2 -9
  68. package/src/session/agent-session.ts +89 -8
  69. package/src/session/session-context.ts +2 -1
  70. package/src/session/session-manager.ts +2 -1
  71. package/src/session/unexpected-stop-classifier.ts +1 -1
  72. package/src/task/index.ts +23 -27
  73. package/src/task/spawn-policy.test.ts +63 -0
  74. package/src/task/spawn-policy.ts +58 -0
  75. package/src/task/types.ts +77 -6
  76. package/src/tools/__tests__/eval-description.test.ts +19 -0
  77. package/src/tools/browser/cmux/cmux-tab.ts +66 -24
  78. package/src/tools/browser/registry.ts +25 -0
  79. package/src/tools/browser/run-cancellation.ts +47 -0
  80. package/src/tools/browser/tab-supervisor.ts +120 -7
  81. package/src/tools/browser/tab-worker.ts +22 -10
  82. package/src/tools/browser.ts +1 -0
  83. package/src/tools/eval.ts +15 -10
  84. package/src/tools/inspect-image.ts +1 -1
  85. package/src/tools/ssh.ts +8 -1
  86. package/src/tools/write.ts +49 -9
  87. package/src/utils/commit-message-generator.ts +0 -1
  88. package/src/utils/image-vision-fallback.ts +2 -3
  89. package/src/utils/title-generator.ts +1 -1
@@ -1,8 +1,6 @@
1
1
  import type { ResolvedThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, ApiKeyResolver, AssistantMessageEventStream, Context, Model, ServiceTierByFamily, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
3
- import { type CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
4
3
  import type { ApiKeyResolverModel } from "../config/api-key-resolver";
5
- import { type CanonicalModelQueryOptions } from "../config/model-registry";
6
4
  import { Settings } from "../config/settings";
7
5
  export interface BenchCommandArgs {
8
6
  models: string[];
@@ -20,9 +18,6 @@ export interface BenchModelRegistry {
20
18
  getAll(): Model<Api>[];
21
19
  getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
22
20
  resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
23
- resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
24
- getCanonicalVariants?(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
25
- getCanonicalId?(model: Model<Api>): string | undefined;
26
21
  hasConfiguredAuth?(model: Model<Api>): boolean;
27
22
  }
28
23
  export interface BenchRuntime {
@@ -1,6 +1,4 @@
1
1
  import type { Api, AssistantMessageEventStream, AuthCredentialSnapshotEntry, Context, Model, OAuthAccess, OAuthAccessResolution, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
2
- import type { CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
3
- import { type CanonicalModelQueryOptions } from "../config/model-registry";
4
2
  import { Settings } from "../config/settings";
5
3
  export interface DryBalanceCommandArgs {
6
4
  model?: string;
@@ -32,9 +30,6 @@ export interface DryBalanceModelRegistry {
32
30
  getAll(): Model<Api>[];
33
31
  getAvailable(): Model<Api>[];
34
32
  getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
35
- getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
36
- resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
37
- getCanonicalId?(model: Model<Api>): string | undefined;
38
33
  }
39
34
  export interface DryBalanceRuntime {
40
35
  modelRegistry: DryBalanceModelRegistry;
@@ -1,5 +1,5 @@
1
1
  import { ModelRegistry } from "../config/model-registry";
2
- export type ModelsAction = "ls" | "find" | "refresh" | "canonical";
2
+ export type ModelsAction = "ls" | "find" | "refresh";
3
3
  export interface ModelsCommandArgs {
4
4
  action: ModelsAction;
5
5
  /** Search substring for `find`, or optional filter for `ls`. */
@@ -7,7 +7,6 @@
7
7
  * Gemini benefits from descriptors in-prompt; other providers keep them in the
8
8
  * tool schemas. `on`/`off` are explicit user overrides.
9
9
  *
10
- * @param modelId Canonical model id (e.g. `gemini-3-pro`); resolve aliases via
11
- * `ModelRegistry.getCanonicalId` before calling so `auto` classifies correctly.
10
+ * @param modelId Model id (e.g. `gemini-3-pro`) used to classify `auto`.
12
11
  */
13
12
  export declare function shouldInlineToolDescriptors(setting: "auto" | "on" | "off" | undefined, modelId: string | undefined): boolean;
@@ -3,11 +3,9 @@ import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-st
3
3
  import { type ModelRefreshStrategy } from "@oh-my-pi/pi-catalog/model-manager";
4
4
  import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
5
5
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
6
- import { type CanonicalModelIndex, type CanonicalModelRecord, type CanonicalModelVariant, type ModelEquivalenceConfig } from "@oh-my-pi/pi-catalog/identity";
7
6
  import type { AuthStorage } from "../session/auth-storage";
8
7
  import { type ApiKeyResolverModel, type ApiKeyResolverOptions } from "./api-key-resolver";
9
8
  import type { ConfigError } from "./config-file";
10
- export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
11
9
  export declare const kNoAuth = "N/A";
12
10
  export declare function isAuthenticated(apiKey: string | undefined | null): apiKey is string;
13
11
  /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
@@ -57,15 +55,6 @@ export interface ProviderDiscoveryState {
57
55
  models: string[];
58
56
  error?: string;
59
57
  }
60
- export interface CanonicalModelQueryOptions {
61
- availableOnly?: boolean;
62
- candidates?: readonly Model<Api>[];
63
- }
64
- /** A canonical record (with query-filtered variants) plus the variant model selected for it. */
65
- export interface CanonicalModelSelection {
66
- record: CanonicalModelRecord;
67
- model: Model<Api>;
68
- }
69
58
  /**
70
59
  * Model registry - loads and manages models, resolves API keys via AuthStorage.
71
60
  */
@@ -113,18 +102,6 @@ export declare class ModelRegistry {
113
102
  * If models.json had errors, returns only built-in models.
114
103
  */
115
104
  getAll(): Model<Api>[];
116
- getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[];
117
- /**
118
- * One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
119
- * record. The per-query state (candidate-selector set, availability memo,
120
- * provider rank, candidate order) is built once, so the whole catalog
121
- * resolves in O(records + candidates) instead of O(records × candidates).
122
- * This is the path the model selector hydrates from synchronously on open.
123
- */
124
- getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[];
125
- getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
126
- resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
127
- getCanonicalId(model: Model<Api>): string | undefined;
128
105
  /**
129
106
  * Get only models that have auth configured.
130
107
  * This is a fast check that doesn't refresh OAuth tokens.
@@ -271,3 +248,4 @@ export interface ProviderConfigInput {
271
248
  premiumMultiplier?: number;
272
249
  }>;
273
250
  }
251
+ export {};
@@ -4,8 +4,8 @@
4
4
  * Layering:
5
5
  * - `matchModel` is the single matching engine. Order: exact `provider/id`
6
6
  * reference (with variant-alias and OpenRouter routed/date fallbacks) →
7
- * exact canonical id → exact bare idretired variant alias
8
- * provider-scoped fuzzy → substring with alias-vs-dated pick.
7
+ * exact bare id → retired variant aliasprovider-scoped fuzzy substring
8
+ * with alias-vs-dated pick.
9
9
  * - `parseModelPatternWithContext`/`parseModelPattern` layer the selector
10
10
  * grammar on top: trailing `:level` thinking suffixes (`splitThinkingSuffix`)
11
11
  * and `@upstream` provider routing (`splitUpstreamRouting`).
@@ -75,9 +75,8 @@ export interface ModelMatchPreferences {
75
75
  /** Providers to deprioritize when no recent usage or provider priority is available. */
76
76
  deprioritizeProviders?: string[];
77
77
  }
78
- export type CanonicalModelRegistry = Partial<Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">>;
79
- export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable"> & Partial<CanonicalModelRegistry>;
80
- type CliModelRegistry = Pick<ModelRegistry, "getAll"> & Partial<CanonicalModelRegistry>;
78
+ export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable">;
79
+ type CliModelRegistry = Pick<ModelRegistry, "getAll">;
81
80
  type InitialModelRegistry = Pick<ModelRegistry, "getAvailable" | "find">;
82
81
  type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "find" | "getApiKey">;
83
82
  export declare function getModelMatchPreferences(settings?: Partial<Pick<Settings, "get" | "getStorage">>): ModelMatchPreferences;
@@ -92,7 +91,6 @@ export interface ParsedModelResult {
92
91
  }
93
92
  export declare function parseModelPattern(pattern: string, availableModels: Model<Api>[], preferences?: ModelMatchPreferences, options?: {
94
93
  allowInvalidThinkingSelectorFallback?: boolean;
95
- modelRegistry?: CanonicalModelRegistry;
96
94
  }): ParsedModelResult;
97
95
  /**
98
96
  * Expand a role alias like "pi/smol" to the configured model string.
@@ -119,7 +117,6 @@ export interface ResolvedModelRoleValue {
119
117
  export declare function resolveModelRoleValue(roleValue: string | undefined, availableModels: Model<Api>[], options?: {
120
118
  settings?: Settings;
121
119
  matchPreferences?: ModelMatchPreferences;
122
- modelRegistry?: CanonicalModelRegistry;
123
120
  }): ResolvedModelRoleValue;
124
121
  interface ExplicitThinkingSelectorOptions {
125
122
  isLiteralModelId?: (provider: string, id: string) => boolean;
@@ -128,7 +125,7 @@ export declare function extractExplicitThinkingSelector(value: string | undefine
128
125
  /**
129
126
  * Resolve a model identifier or pattern to a Model instance.
130
127
  */
131
- export declare function resolveModelFromString(value: string, available: Model<Api>[], matchPreferences?: ModelMatchPreferences, modelRegistry?: CanonicalModelRegistry): Model<Api> | undefined;
128
+ export declare function resolveModelFromString(value: string, available: Model<Api>[], matchPreferences?: ModelMatchPreferences): Model<Api> | undefined;
132
129
  /**
133
130
  * Resolve a model from configured roles, honoring order and overrides.
134
131
  */
@@ -137,7 +134,6 @@ export declare function resolveModelFromSettings(options: {
137
134
  availableModels: Model<Api>[];
138
135
  matchPreferences?: ModelMatchPreferences;
139
136
  roleOrder?: readonly ModelRole[];
140
- modelRegistry?: CanonicalModelRegistry;
141
137
  }): Model<Api> | undefined;
142
138
  /**
143
139
  * Resolve a list of override patterns to the first matching model.
@@ -177,7 +173,7 @@ export declare function resolveModelOverrideWithAuthFallback(modelPatterns: stri
177
173
  /**
178
174
  * Resolve a list of role patterns to the first matching model.
179
175
  */
180
- export declare function resolveRoleSelection(roles: readonly string[], settings: Settings, availableModels: Model<Api>[], modelRegistry?: CanonicalModelRegistry): {
176
+ export declare function resolveRoleSelection(roles: readonly string[], settings: Settings, availableModels: Model<Api>[]): {
181
177
  model: Model<Api>;
182
178
  thinkingLevel?: ThinkingLevel;
183
179
  } | undefined;
@@ -189,7 +185,7 @@ export declare function resolveRoleSelection(roles: readonly string[], settings:
189
185
  * the `slow` role itself, never inherits the primary's model. Returns undefined
190
186
  * only when no candidate in the resolved chain is available.
191
187
  */
192
- export declare function resolveAdvisorRoleSelection(settings: Settings, availableModels: Model<Api>[], modelRegistry?: CanonicalModelRegistry): {
188
+ export declare function resolveAdvisorRoleSelection(settings: Settings, availableModels: Model<Api>[]): {
193
189
  model: Model<Api>;
194
190
  thinkingLevel?: ThinkingLevel;
195
191
  } | undefined;
@@ -204,7 +200,7 @@ export declare function resolveAdvisorRoleSelection(settings: Settings, availabl
204
200
  * The algorithm tries to match the full pattern first, then progressively
205
201
  * strips colon-suffixes to find a match.
206
202
  */
207
- export declare function resolveModelScope(patterns: string[], modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">, preferences?: ModelMatchPreferences): Promise<ScopedModel[]>;
203
+ export declare function resolveModelScope(patterns: string[], modelRegistry: Pick<ModelRegistry, "getAvailable">, preferences?: ModelMatchPreferences): Promise<ScopedModel[]>;
208
204
  /**
209
205
  * Resolve the set of models a session is allowed to use, given the active
210
206
  * settings. Starts from `modelRegistry.getAvailable()` (so disabled providers
@@ -217,7 +213,7 @@ export declare function resolveModelScope(patterns: string[], modelRegistry: Pic
217
213
  * any pattern — callers MUST treat this as "no usable model" rather than
218
214
  * falling back to the global default (see issue #1022).
219
215
  */
220
- export declare function resolveAllowedModels(modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">, settings: Settings | undefined, preferences?: ModelMatchPreferences): Promise<Model<Api>[]>;
216
+ export declare function resolveAllowedModels(modelRegistry: Pick<ModelRegistry, "getAvailable">, settings: Settings | undefined, preferences?: ModelMatchPreferences): Promise<Model<Api>[]>;
221
217
  /**
222
218
  * Synchronous subset of {@link resolveAllowedModels} for contexts where async is unavailable
223
219
  * (e.g. `getAvailableModels()` which is called from the ACP model-list advertisement, RPC
@@ -225,7 +221,6 @@ export declare function resolveAllowedModels(modelRegistry: Pick<ModelRegistry,
225
221
  * `enabledModels` scope semantics as startup resolution:
226
222
  *
227
223
  * - Glob selectors match `provider/modelId` and bare model id
228
- * - Exact canonical ids expand to all available concrete variants
229
224
  * - Exact `provider/modelId`, bare ids, provider-scoped fuzzy, and substring selectors
230
225
  * resolve through the shared model-pattern matcher
231
226
  * - Optional `:thinkingLevel` suffixes are stripped only when valid
@@ -235,7 +230,7 @@ export declare function resolveAllowedModels(modelRegistry: Pick<ModelRegistry,
235
230
  * a UI picker should treat an empty list as "hide the picker entry", matching how the SDK
236
231
  * surfaces the same misconfiguration during session initialization.
237
232
  */
238
- export declare function filterAvailableModelsByEnabledPatterns(available: Model<Api>[], patterns: readonly string[], registry: Pick<ModelRegistry, "getCanonicalVariants">): Model<Api>[];
233
+ export declare function filterAvailableModelsByEnabledPatterns(available: Model<Api>[], patterns: readonly string[]): Model<Api>[];
239
234
  export interface ResolveCliModelResult {
240
235
  model: Model<Api> | undefined;
241
236
  selector?: string;
@@ -245,6 +245,9 @@ export declare const ModelOverrideSchema: import("arktype/internal/variants/obje
245
245
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
246
246
  endpoint?: string | undefined;
247
247
  model?: string | undefined;
248
+ v2StreamingEnabled?: boolean | undefined;
249
+ v2Endpoint?: string | undefined;
250
+ streamingEndpoint?: string | undefined;
248
251
  } | undefined;
249
252
  }, {}>;
250
253
  export type ModelOverride = typeof ModelOverrideSchema.infer;
@@ -368,6 +371,9 @@ export declare const ModelsConfigSchema: import("arktype/internal/variants/objec
368
371
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
369
372
  endpoint?: string | undefined;
370
373
  model?: string | undefined;
374
+ v2StreamingEnabled?: boolean | undefined;
375
+ v2Endpoint?: string | undefined;
376
+ streamingEndpoint?: string | undefined;
371
377
  } | undefined;
372
378
  authHeader?: boolean | undefined;
373
379
  auth?: "apiKey" | "none" | "oauth" | undefined;
@@ -524,6 +530,9 @@ export declare const ModelsConfigSchema: import("arktype/internal/variants/objec
524
530
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
525
531
  endpoint?: string | undefined;
526
532
  model?: string | undefined;
533
+ v2StreamingEnabled?: boolean | undefined;
534
+ v2Endpoint?: string | undefined;
535
+ streamingEndpoint?: string | undefined;
527
536
  } | undefined;
528
537
  }[] | undefined;
529
538
  modelOverrides?: {
@@ -674,6 +683,9 @@ export declare const ModelsConfigSchema: import("arktype/internal/variants/objec
674
683
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
675
684
  endpoint?: string | undefined;
676
685
  model?: string | undefined;
686
+ v2StreamingEnabled?: boolean | undefined;
687
+ v2Endpoint?: string | undefined;
688
+ streamingEndpoint?: string | undefined;
677
689
  } | undefined;
678
690
  };
679
691
  } | undefined;
@@ -681,11 +693,5 @@ export declare const ModelsConfigSchema: import("arktype/internal/variants/objec
681
693
  transport?: "pi-native" | undefined;
682
694
  };
683
695
  } | undefined;
684
- equivalence?: {
685
- overrides?: {
686
- [x: string]: string;
687
- } | undefined;
688
- exclude?: string[] | undefined;
689
- } | undefined;
690
696
  }, {}>;
691
697
  export type ModelsConfig = typeof ModelsConfigSchema.infer;
@@ -141,6 +141,9 @@ export declare const ModelsConfigFile: ConfigFile<{
141
141
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
142
142
  endpoint?: string | undefined;
143
143
  model?: string | undefined;
144
+ v2StreamingEnabled?: boolean | undefined;
145
+ v2Endpoint?: string | undefined;
146
+ streamingEndpoint?: string | undefined;
144
147
  } | undefined;
145
148
  authHeader?: boolean | undefined;
146
149
  auth?: "apiKey" | "none" | "oauth" | undefined;
@@ -282,6 +285,9 @@ export declare const ModelsConfigFile: ConfigFile<{
282
285
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
283
286
  endpoint?: string | undefined;
284
287
  model?: string | undefined;
288
+ v2StreamingEnabled?: boolean | undefined;
289
+ v2Endpoint?: string | undefined;
290
+ streamingEndpoint?: string | undefined;
285
291
  } | undefined;
286
292
  }[] | undefined;
287
293
  modelOverrides?: {
@@ -417,6 +423,9 @@ export declare const ModelsConfigFile: ConfigFile<{
417
423
  api?: "anthropic-messages" | "azure-openai-responses" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined;
418
424
  endpoint?: string | undefined;
419
425
  model?: string | undefined;
426
+ v2StreamingEnabled?: boolean | undefined;
427
+ v2Endpoint?: string | undefined;
428
+ streamingEndpoint?: string | undefined;
420
429
  } | undefined;
421
430
  };
422
431
  } | undefined;
@@ -424,10 +433,4 @@ export declare const ModelsConfigFile: ConfigFile<{
424
433
  transport?: "pi-native" | undefined;
425
434
  };
426
435
  } | undefined;
427
- equivalence?: {
428
- overrides?: {
429
- [x: string]: string;
430
- } | undefined;
431
- exclude?: string[] | undefined;
432
- } | undefined;
433
436
  }>;
@@ -919,7 +919,7 @@ export declare const SETTINGS_SCHEMA: {
919
919
  readonly tab: "model";
920
920
  readonly group: "Thinking";
921
921
  readonly label: "Loop Guard";
922
- readonly description: "Enable automatic stream loop detection for Gemini and DeepSeek models";
922
+ readonly description: "Enable automatic stream loop detection for model reasoning and prose";
923
923
  };
924
924
  };
925
925
  readonly "model.loopGuard.checkAssistantContent": {
@@ -942,6 +942,36 @@ export declare const SETTINGS_SCHEMA: {
942
942
  readonly description: "When a Gemini reasoning stream emits many consecutive planning headers without calling a tool, interrupt it and inject a reminder to issue a tool call (requires Loop Guard)";
943
943
  };
944
944
  };
945
+ readonly "model.toolCallLoopGuard.enabled": {
946
+ readonly type: "boolean";
947
+ readonly default: true;
948
+ readonly ui: {
949
+ readonly tab: "model";
950
+ readonly group: "Thinking";
951
+ readonly label: "Tool-Call Loop Guard";
952
+ readonly description: "Detect consecutive identical tool calls across turns and inject a corrective steer";
953
+ };
954
+ };
955
+ readonly "model.toolCallLoopGuard.threshold": {
956
+ readonly type: "number";
957
+ readonly default: 5;
958
+ readonly ui: {
959
+ readonly tab: "model";
960
+ readonly group: "Thinking";
961
+ readonly label: "Tool-Call Loop Threshold";
962
+ readonly description: "Consecutive identical tool calls required before the corrective steer is injected";
963
+ };
964
+ };
965
+ readonly "model.toolCallLoopGuard.exemptTools": {
966
+ readonly type: "array";
967
+ readonly default: string[];
968
+ readonly ui: {
969
+ readonly tab: "model";
970
+ readonly group: "Thinking";
971
+ readonly label: "Tool-Call Loop Exempt Tools";
972
+ readonly description: "Tool names that may repeat consecutively without triggering the cross-turn loop guard";
973
+ };
974
+ };
945
975
  readonly inlineToolDescriptors: {
946
976
  readonly type: "enum";
947
977
  readonly values: readonly ["auto", "on", "off"];
@@ -119,6 +119,21 @@ export declare class ExtensionRunner {
119
119
  createCommandContext(): ExtensionCommandContext;
120
120
  emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>>;
121
121
  emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined>;
122
+ /**
123
+ * Emit a `tool_call` event to every subscribed extension before the tool executes.
124
+ *
125
+ * Each handler is bounded by `extensionHandlerTimeoutMs` (default 30s). This
126
+ * matches the timeout policy already applied to `emitToolResult` and every
127
+ * other handler routed through `#runHandlerWithTimeout`; without it a single
128
+ * hung extension (unresolved `await`, network call with no timeout) would
129
+ * park `ExtensionToolWrapper.execute` indefinitely and freeze tool
130
+ * dispatch — see issue #3948.
131
+ *
132
+ * On-timeout policy: **fail-closed** (return `{ block: true }`). This is
133
+ * symmetric with the existing error path below and safer for a
134
+ * pre-execution gate — an unresponsive extension MUST NOT be treated as
135
+ * silent consent to run the tool.
136
+ */
122
137
  emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined>;
123
138
  emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined>;
124
139
  emitUserPython(event: UserPythonEvent): Promise<UserPythonEventResult | undefined>;
@@ -10,9 +10,13 @@ export declare const WARMUP_TIMEOUT_MS = 5000;
10
10
  * Get or create an LSP client for the given server configuration and working directory.
11
11
  * @param config - Server configuration
12
12
  * @param cwd - Working directory
13
- * @param initTimeoutMs - Optional timeout for the initialize request (defaults to 30s)
13
+ * @param initTimeoutMs - Optional hard deadline for the initialize handshake (warmup / other
14
+ * short-lived callers). When set it takes precedence over `signal` inside `sendRequest`.
15
+ * @param signal - Optional caller abort signal. Threaded into the initialize `sendRequest`
16
+ * and the `initialized` notification so a wedged server surfaces the caller's
17
+ * timeout/cancel instead of falling back to the internal 30s default.
14
18
  */
15
- export declare function getOrCreateClient(config: ServerConfig, cwd: string, initTimeoutMs?: number): Promise<LspClient>;
19
+ export declare function getOrCreateClient(config: ServerConfig, cwd: string, initTimeoutMs?: number, signal?: AbortSignal): Promise<LspClient>;
16
20
  /**
17
21
  * Ensure a file is opened in the LSP client.
18
22
  * Sends didOpen notification if the file is not already tracked.
@@ -59,8 +63,10 @@ export declare function shutdownClient(key: string): Promise<void>;
59
63
  export declare function sendRequest(client: LspClient, method: string, params: unknown, signal?: AbortSignal, timeoutMs?: number): Promise<unknown>;
60
64
  /**
61
65
  * Send an LSP notification (no response expected).
66
+ * `signal` bounds the underlying `sink.flush()` — without it a server that
67
+ * stops draining stdin blocks every future write on the client's write queue.
62
68
  */
63
- export declare function sendNotification(client: LspClient, method: string, params: unknown): Promise<void>;
69
+ export declare function sendNotification(client: LspClient, method: string, params: unknown, signal?: AbortSignal): Promise<void>;
64
70
  /**
65
71
  * Shutdown all LSP clients.
66
72
  */
@@ -36,11 +36,6 @@ export declare class ToolArgsRevealController {
36
36
  setTarget(id: string, partialJson: string, target: ToolArgsRevealTarget): Record<string, unknown>;
37
37
  /** Attach the component future ticks push frames into. */
38
38
  bind(id: string, component: ToolArgsRevealComponent): void;
39
- /** Migrate a live reveal entry from a placeholder key onto the real
40
- * tool-call id once the owned-dialect parser assigns it. No-op when no
41
- * entry exists under `from` (smoothing disabled, or the JSON already
42
- * closed and `finish` cleared it). */
43
- rekey(from: string, to: string): void;
44
39
  /** Final arguments arrived (the JSON closed): drop the reveal so the
45
40
  * caller's final-args render wins immediately, mirroring how assistant
46
41
  * text snaps to the full message at message_end. */
@@ -0,0 +1,17 @@
1
+ /** Default agent used when a session has unrestricted spawning. */
2
+ export declare const DEFAULT_SPAWN_AGENT = "task";
3
+ /** Spawn policy derived from a parent agent's `spawns` frontmatter. */
4
+ export interface ResolvedSpawnPolicy {
5
+ /** True when at least one subagent may be spawned. */
6
+ enabled: boolean;
7
+ /** Agent used when the caller omits the agent field. */
8
+ defaultAgent: string;
9
+ /** Explicitly allowed agents, or `null` when the policy is unrestricted. */
10
+ allowedAgents: readonly string[] | null;
11
+ /** Text used in spawn rejection messages. */
12
+ allowedErrorText: string;
13
+ /** Backtick-quoted explicit agents for prompt descriptions. */
14
+ allowedPromptText?: string;
15
+ }
16
+ /** Resolves spawn frontmatter into the default and prompt/error surfaces. */
17
+ export declare function resolveSpawnPolicy(parentSpawns: string | boolean | null | undefined): ResolvedSpawnPolicy;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Usage } from "@oh-my-pi/pi-ai";
3
+ import { type BaseType } from "arktype";
3
4
  import type { AgentSessionEvent } from "../session/agent-session";
4
5
  import type { NestedRepoPatch } from "./worktree";
5
6
  /** Source of an agent definition */
@@ -117,11 +118,16 @@ declare const ALL_TASK_SCHEMAS: readonly [import("arktype/internal/variants/obje
117
118
  type DynamicTaskSchema = (typeof ALL_TASK_SCHEMAS)[number];
118
119
  export type TaskSchema = typeof taskSchema;
119
120
  /** Active task tool parameter schema for the current isolation / batch flags */
120
- export type TaskToolSchemaInstance = DynamicTaskSchema;
121
+ export type TaskToolSchemaInstance = DynamicTaskSchema | BaseType;
121
122
  export declare function getTaskSchema(options: {
122
123
  isolationEnabled: boolean;
123
124
  batchEnabled: boolean;
124
125
  }): DynamicTaskSchema;
126
+ export declare function getTaskSchema(options: {
127
+ isolationEnabled: boolean;
128
+ batchEnabled: boolean;
129
+ defaultAgent: string;
130
+ }): TaskToolSchemaInstance;
125
131
  /**
126
132
  * Runtime params union over both wire shapes. The model sees exactly one shape
127
133
  * (`{ agent, context, tasks[] }` when `task.batch` is on, `{ agent, ...item }`
@@ -129,7 +135,7 @@ export declare function getTaskSchema(options: {
129
135
  * transcripts using the flat form keep working under either setting.
130
136
  */
131
137
  export interface TaskParams {
132
- /** Agent type to spawn; defaults to `"task"` (the general-purpose worker) when omitted. */
138
+ /** Agent type to spawn; omitted values resolve from the session spawn policy. */
133
139
  agent?: string;
134
140
  /** Stable agent id (flat form); default = generated AdjectiveNoun. */
135
141
  id?: string;
@@ -1,5 +1,5 @@
1
1
  import { JsRuntime } from "../../../eval/js/shared/runtime";
2
- import type { ToolSession } from "../../../sdk";
2
+ import type { ToolSession } from "../../index";
3
3
  import { type AriaSnapshotOptions } from "../aria/aria-snapshot";
4
4
  import { type ReadableFormat } from "../readable";
5
5
  import type { Observation, ReadyInfo, RunResultOk, ScreenshotResult, SessionSnapshot } from "../tab-protocol";
@@ -19,7 +19,7 @@ interface RunContext {
19
19
  session: SessionSnapshot;
20
20
  displays: RunResultOk["displays"];
21
21
  screenshots: ScreenshotResult[];
22
- signal?: AbortSignal;
22
+ signal: AbortSignal;
23
23
  timeoutMs: number;
24
24
  }
25
25
  type WaitUntil = "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
@@ -53,4 +53,6 @@ export declare function holdBrowser(handle: BrowserHandle): void;
53
53
  export declare function releaseBrowser(handle: BrowserHandle, opts: {
54
54
  kill: boolean;
55
55
  }): Promise<void>;
56
+ /** Test-only accessor for the module-global browsers map. */
57
+ export declare function getBrowsersMapForTest(): ReadonlyMap<string, BrowserHandle>;
56
58
  export {};
@@ -0,0 +1,4 @@
1
+ /** Sleeps inside evaluated browser code while honoring the owning run's cancellation signal. */
2
+ export declare function waitForBrowserRun(ms: number, signal: AbortSignal): Promise<void>;
3
+ /** Binds a long-lived browser facade to one evaluated run's abort signal. */
4
+ export declare function bindBrowserRunFacade<T extends object>(target: T, signal: AbortSignal): T;
@@ -1,4 +1,4 @@
1
- import type { ToolSession } from "../../sdk";
1
+ import type { ToolSession } from "../index";
2
2
  import { CmuxTab } from "./cmux/cmux-tab";
3
3
  import { type BrowserHandle, type BrowserKindTag, type CmuxBrowserHandle, type PuppeteerBrowserHandle } from "./registry";
4
4
  import type { ReadyInfo, RunResultOk, Transferable, WorkerInbound, WorkerInitPayload, WorkerOutbound } from "./tab-protocol";
@@ -26,6 +26,13 @@ interface TabSessionBase<TBrowser extends BrowserHandle = BrowserHandle> {
26
26
  pending: Map<string, PendingRun>;
27
27
  dialogPolicy?: DialogPolicy;
28
28
  kindTag: BrowserKindTag;
29
+ /**
30
+ * Session id of the caller that CREATED the tab. Preserved across reuse so
31
+ * that dispose of the creating session can reap browser resources without
32
+ * yanking the tab out from under a subagent that only reused it.
33
+ * Undefined when the acquirer did not identify itself.
34
+ */
35
+ ownerSessionId?: string;
29
36
  }
30
37
  export interface WorkerTabSession extends TabSessionBase<PuppeteerBrowserHandle> {
31
38
  backend: "worker";
@@ -51,6 +58,12 @@ export interface AcquireTabOptions {
51
58
  timeoutMs: number;
52
59
  dialogs?: DialogPolicy;
53
60
  cmuxSurface?: string;
61
+ /**
62
+ * Session id of the acquirer. Recorded on the tab when created (never on
63
+ * reuse) so `releaseTabsForOwner` can walk the shared tabs map on session
64
+ * dispose. Optional — omitting it opts the tab out of session-scoped reap.
65
+ */
66
+ ownerSessionId?: string;
54
67
  }
55
68
  export interface AcquireTabResult {
56
69
  tab: TabSession;
@@ -71,5 +84,21 @@ export declare function runInTab(name: string, opts: RunInTabOptions): Promise<R
71
84
  export declare function releaseTab(name: string, opts?: ReleaseTabOptions): Promise<boolean>;
72
85
  export declare function releaseAllTabs(opts?: ReleaseTabOptions): Promise<number>;
73
86
  export declare function dropHeadlessTabs(): Promise<void>;
87
+ /**
88
+ * Release every tab created by the given session id. Invoked from
89
+ * `AgentSession.dispose()` so headless/spawned Chromium and workers the
90
+ * session opened do not leak into the long-lived process — the module-global
91
+ * `tabs`/`browsers` maps that back this tool are not otherwise walked by
92
+ * session teardown. (Issue #3963.)
93
+ *
94
+ * Ownership is recorded ONLY on tab creation (`acquireTab` with
95
+ * `ownerSessionId`), never on reuse: a subagent re-driving a tab another
96
+ * session opened will not yank teardown responsibility away from the
97
+ * creator. Tabs opened with no owner (e.g. from an SDK caller that doesn't
98
+ * identify a session) are skipped and must be released explicitly.
99
+ */
100
+ export declare function releaseTabsForOwner(ownerId: string, opts?: ReleaseTabOptions): Promise<number>;
101
+ /** Test-only accessor for the module-global tabs map. */
102
+ export declare function getTabsMapForTest(): ReadonlyMap<string, TabSession>;
74
103
  export declare function initializeTabWorkerForTest(worker: WorkerHandle, payload: WorkerInitPayload, timeoutMs: number): Promise<ReadyInfo>;
75
104
  export {};
@@ -35,13 +35,10 @@ export interface EvalToolDescriptionOptions {
35
35
  rb?: boolean;
36
36
  jl?: boolean;
37
37
  /**
38
- * Whether `agent()` is allowed in this session. Driven by the parent's
39
- * spawn policy (`getSessionSpawns`). Defaults to `true` for backward
40
- * compatibility — when the session forbids spawning, the prelude doc
41
- * omits the `agent()` entry so the model does not promise itself a
42
- * helper that will only ever throw "spawns disabled".
38
+ * Parent spawn policy (`getSessionSpawns`). `true`/omitted means unrestricted,
39
+ * `false`/`""` hides `agent()`, and a comma list drives the advertised default.
43
40
  */
44
- spawns?: boolean;
41
+ spawns?: boolean | string | null;
45
42
  }
46
43
  export declare function getEvalToolDescription(options?: EvalToolDescriptionOptions): string;
47
44
  export interface EvalToolOptions {
@@ -44,7 +44,7 @@ export declare class WriteTool implements AgentTool<typeof writeSchema, WriteToo
44
44
  /** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
45
45
  matcherDigest(args: unknown): string | undefined;
46
46
  constructor(session: ToolSession);
47
- execute(_toolCallId: string, { path: rawPath, content }: WriteParams, signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<WriteToolDetails>, context?: AgentToolContext): Promise<AgentToolResult<WriteToolDetails>>;
47
+ execute(_toolCallId: string, { path: rawPath, content }: WriteParams, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<WriteToolDetails>, context?: AgentToolContext): Promise<AgentToolResult<WriteToolDetails>>;
48
48
  }
49
49
  interface WriteRenderArgs {
50
50
  path?: string;
@@ -4,7 +4,7 @@ import type { ModelRegistry } from "../config/model-registry";
4
4
  import type { Settings } from "../config/settings";
5
5
  import { type LocalProtocolOptions } from "../internal-urls";
6
6
  /** Registry surface needed to resolve a vision model and authorize requests. */
7
- export type VisionFallbackRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey" | "resolver"> & Partial<Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">>;
7
+ export type VisionFallbackRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey" | "resolver">;
8
8
  export interface DescribeAttachedImagesDeps {
9
9
  /** Active (text-only) model the prompt is destined for. */
10
10
  activeModel: Model<Api>;