@oh-my-pi/pi-coding-agent 16.1.19 → 16.1.20

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.
@@ -2,6 +2,12 @@ import { type GalleryFixture } from "./gallery-fixtures";
2
2
  /** Lifecycle states the gallery renders, in display order. */
3
3
  export declare const GALLERY_STATES: readonly ["streaming", "progress", "success", "error"];
4
4
  export type GalleryState = (typeof GALLERY_STATES)[number];
5
+ /** User-facing labels printed above each rendered lifecycle state. */
6
+ export declare const GALLERY_STATE_LABELS: Record<GalleryState, string>;
7
+ /** Accepted `--state` tokens, including legacy lifecycle names and displayed labels. */
8
+ export declare const GALLERY_STATE_TOKENS: string[];
9
+ /** Normalize user-provided `--state` tokens to the internal gallery lifecycle states. */
10
+ export declare function parseGalleryStates(states: readonly string[] | undefined): GalleryState[] | undefined;
5
11
  export interface GalleryCommandArgs {
6
12
  /** Render width in columns (defaults to terminal width, clamped). */
7
13
  width?: number;
@@ -12,7 +12,7 @@ export default class Gallery extends Command {
12
12
  state: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
13
13
  char: string;
14
14
  description: string;
15
- options: ("error" | "progress" | "streaming" | "success")[];
15
+ options: string[];
16
16
  multiple: true;
17
17
  };
18
18
  width: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
@@ -0,0 +1,34 @@
1
+ import type { ServiceTier } from "@oh-my-pi/pi-ai";
2
+ import type { SubmenuOption } from "./settings-schema";
3
+ /**
4
+ * Service-tier setting values shared by every "Service Tier" setting. `"none"`
5
+ * is the omit-the-parameter sentinel; the remaining values mirror
6
+ * {@link ServiceTier}.
7
+ */
8
+ export declare const SERVICE_TIER_SETTING_VALUES: readonly ["none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
9
+ export type ServiceTierSettingValue = (typeof SERVICE_TIER_SETTING_VALUES)[number];
10
+ /** Variant value set for scoped service-tier settings (subagent/advisor) that can defer to the main agent. */
11
+ export declare const SERVICE_TIER_INHERIT_SETTING_VALUES: readonly ["inherit", "none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
12
+ export type ServiceTierInheritSettingValue = (typeof SERVICE_TIER_INHERIT_SETTING_VALUES)[number];
13
+ /** Submenu descriptions shared by the base `serviceTier` setting. */
14
+ export declare const SERVICE_TIER_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierSettingValue>>;
15
+ /** Submenu descriptions for inherit-capable service-tier settings. */
16
+ export declare const SERVICE_TIER_INHERIT_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierInheritSettingValue>>;
17
+ /**
18
+ * Resolve a service-tier setting value to the wire {@link ServiceTier} (or
19
+ * `undefined` to omit). `"inherit"` defers to `inherited`; `"none"` omits.
20
+ */
21
+ export declare function resolveServiceTierSetting(value: string, inherited: ServiceTier | undefined): ServiceTier | undefined;
22
+ /**
23
+ * Resolve the `serviceTier` *setting value* to stamp onto a subagent's settings
24
+ * snapshot.
25
+ *
26
+ * - A concrete `subagentSetting` (`"none"` or a tier) wins outright.
27
+ * - `"inherit"` defers to the parent's live effective tier when the caller has a
28
+ * live session (`inherited` passed as `ServiceTier | null`, where `null` means
29
+ * the parent explicitly has no tier — e.g. `/fast off`). When no live session
30
+ * is available (`inherited === undefined`, e.g. cold subagent revive) it falls
31
+ * back to the parent's configured `serviceTier` setting so behavior matches a
32
+ * plain settings snapshot.
33
+ */
34
+ export declare function resolveSubagentServiceTier(subagentSetting: string, configuredTier: ServiceTierSettingValue, inherited: ServiceTier | null | undefined): ServiceTierSettingValue;
@@ -1153,39 +1153,32 @@ export declare const SETTINGS_SCHEMA: {
1153
1153
  readonly group: "Sampling";
1154
1154
  readonly label: "Service Tier";
1155
1155
  readonly description: 'Processing priority hint (none = omit). OpenAI accepts the tier values directly; Anthropic realizes `priority` as `speed: "fast"` on supported Opus models. Scoped values target one family.';
1156
- readonly options: readonly [{
1157
- readonly value: "none";
1158
- readonly label: "None";
1159
- readonly description: "Omit service_tier parameter";
1160
- }, {
1161
- readonly value: "auto";
1162
- readonly label: "Auto";
1163
- readonly description: "Use provider default tier selection (OpenAI)";
1164
- }, {
1165
- readonly value: "default";
1166
- readonly label: "Default";
1167
- readonly description: "Standard priority processing (OpenAI)";
1168
- }, {
1169
- readonly value: "flex";
1170
- readonly label: "Flex";
1171
- readonly description: "Flexible capacity tier when available (OpenAI)";
1172
- }, {
1173
- readonly value: "scale";
1174
- readonly label: "Scale";
1175
- readonly description: "Scale Tier credits when available (OpenAI)";
1176
- }, {
1177
- readonly value: "priority";
1178
- readonly label: "Priority";
1179
- readonly description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)";
1180
- }, {
1181
- readonly value: "openai-only";
1182
- readonly label: "Priority (OpenAI only)";
1183
- readonly description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere";
1184
- }, {
1185
- readonly value: "claude-only";
1186
- readonly label: "Priority (Claude only)";
1187
- readonly description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)";
1188
- }];
1156
+ readonly options: readonly SubmenuOption<"auto" | "claude-only" | "default" | "flex" | "none" | "openai-only" | "priority" | "scale">[];
1157
+ };
1158
+ };
1159
+ readonly serviceTierSubagent: {
1160
+ readonly type: "enum";
1161
+ readonly values: readonly ["inherit", "none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
1162
+ readonly default: "inherit";
1163
+ readonly ui: {
1164
+ readonly tab: "model";
1165
+ readonly group: "Sampling";
1166
+ readonly label: "Service Tier - Subagent";
1167
+ readonly description: "Service Tier for spawned task/eval subagents. Inherit = match the main agent's live tier (tracks /fast); pick a value to scope subagents independently.";
1168
+ readonly options: readonly SubmenuOption<"auto" | "claude-only" | "default" | "flex" | "inherit" | "none" | "openai-only" | "priority" | "scale">[];
1169
+ };
1170
+ };
1171
+ readonly serviceTierAdvisor: {
1172
+ readonly type: "enum";
1173
+ readonly values: readonly ["inherit", "none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
1174
+ readonly default: "none";
1175
+ readonly ui: {
1176
+ readonly tab: "model";
1177
+ readonly group: "Sampling";
1178
+ readonly label: "Service Tier - Advisor";
1179
+ readonly description: "Service Tier for the advisor model. None = standard processing; Inherit = match the main agent's live tier; pick a value (e.g. Priority) to run the advisor on a faster serving path.";
1180
+ readonly options: readonly SubmenuOption<"auto" | "claude-only" | "default" | "flex" | "inherit" | "none" | "openai-only" | "priority" | "scale">[];
1181
+ readonly condition: "advisorEnabled";
1189
1182
  };
1190
1183
  };
1191
1184
  readonly fastModeScope: {
@@ -4243,6 +4236,16 @@ export declare const SETTINGS_SCHEMA: {
4243
4236
  readonly description: "Obfuscate secrets before sending to AI providers";
4244
4237
  };
4245
4238
  };
4239
+ readonly "providers.ollama-cloud.maxConcurrency": {
4240
+ readonly type: "number";
4241
+ readonly default: 3;
4242
+ readonly ui: {
4243
+ readonly tab: "providers";
4244
+ readonly group: "Services";
4245
+ readonly label: "Ollama Cloud Max Concurrency";
4246
+ readonly description: "Maximum concurrent Ollama Cloud subagent runs per process; 0 disables the provider-specific limit";
4247
+ };
4248
+ };
4246
4249
  readonly "providers.webSearch": {
4247
4250
  readonly type: "enum";
4248
4251
  readonly values: readonly ["auto", ...SearchProviderId[]];
@@ -760,6 +760,8 @@ export declare class AgentSession {
760
760
  abort(options?: {
761
761
  goalReason?: "interrupted" | "internal";
762
762
  reason?: string;
763
+ /** Internal `/compact` startup keeps the manual-compaction marker alive while aborting the active turn. */
764
+ preserveCompaction?: boolean;
763
765
  }): Promise<void>;
764
766
  /**
765
767
  * Start a new session, optionally with initial messages and parent tracking.
@@ -4,6 +4,7 @@
4
4
  * Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
5
5
  */
6
6
  import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
7
+ import type { ServiceTier } from "@oh-my-pi/pi-ai";
7
8
  import type { Rule } from "../capability/rule";
8
9
  import { ModelRegistry } from "../config/model-registry";
9
10
  import type { PromptTemplate } from "../config/prompt-templates";
@@ -119,6 +120,13 @@ export interface ExecutorOptions {
119
120
  authStorage?: AuthStorage;
120
121
  modelRegistry?: ModelRegistry;
121
122
  settings?: Settings;
123
+ /**
124
+ * Parent session's live effective service tier, the source of truth for a
125
+ * subagent whose `serviceTierSubagent` is `"inherit"`. `null` = the parent
126
+ * explicitly has no tier (e.g. `/fast off`); omitted = no live session, so
127
+ * inherit falls back to the configured `serviceTier` setting.
128
+ */
129
+ parentServiceTier?: ServiceTier | null;
122
130
  /** Override local:// protocol options so subagent shares parent's local:// root */
123
131
  localProtocolOptions?: LocalProtocolOptions;
124
132
  /**
@@ -188,7 +196,7 @@ export declare function finalizeSubprocessOutput(args: FinalizeSubprocessOutputA
188
196
  * Create proxy tools that reuse the parent's MCP connections.
189
197
  */
190
198
  export declare function createMCPProxyTools(mcpManager: MCPManager): CustomTool[];
191
- export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>): Settings;
199
+ export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?: ServiceTier | null): Settings;
192
200
  /**
193
201
  * Run a single agent in-process.
194
202
  */
@@ -33,6 +33,22 @@ export declare function mapWithConcurrencyLimit<T, R>(items: T[], concurrency: n
33
33
  export declare class Semaphore {
34
34
  #private;
35
35
  constructor(max: number);
36
- acquire(): Promise<void>;
36
+ /**
37
+ * Resolves when a slot is available. Pass an `AbortSignal` so callers that
38
+ * stop waiting (parent task cancelled, wall-clock budget elapsed) also stop
39
+ * occupying a queue slot — otherwise a later `release()` would resolve the
40
+ * abandoned waiter, permanently shrinking effective concurrency for the
41
+ * remaining lifetime of the process (issue #3464 review feedback).
42
+ */
43
+ acquire(signal?: AbortSignal): Promise<void>;
37
44
  release(): void;
45
+ /**
46
+ * Adjust the maximum concurrency in place. Raising the ceiling immediately
47
+ * admits queued waiters that now fit; lowering it lets in-flight holders
48
+ * drain naturally (new acquires keep blocking until `#current` falls below
49
+ * the new max). Resizing the single shared instance — instead of replacing
50
+ * it — keeps in-flight slots counted, so a runtime or mixed limit change can
51
+ * never push concurrency past the cap (issue #3464 review feedback).
52
+ */
53
+ resize(max: number): void;
38
54
  }
@@ -1,6 +1,6 @@
1
1
  import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
2
2
  import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
3
- import type { FetchImpl, ImageContent, Model, ToolChoice } from "@oh-my-pi/pi-ai";
3
+ import type { FetchImpl, ImageContent, Model, ServiceTier, ToolChoice } from "@oh-my-pi/pi-ai";
4
4
  import type { AsyncJobManager } from "../async/job-manager";
5
5
  import type { Rule } from "../capability/rule";
6
6
  import type { PromptTemplate } from "../config/prompt-templates";
@@ -191,6 +191,8 @@ export interface ToolSession {
191
191
  getActiveModelString?: () => string | undefined;
192
192
  /** Get the current session model object (provider/api capabilities), regardless of how it was chosen. */
193
193
  getActiveModel?: () => Model | undefined;
194
+ /** Get the session's live effective service tier (undefined = none). Source of truth for subagent `serviceTierSubagent: inherit`. */
195
+ getServiceTier?: () => ServiceTier | undefined;
194
196
  /** Auth storage for passing to subagents (avoids re-discovery) */
195
197
  authStorage?: import("../session/auth-storage").AuthStorage;
196
198
  /** Model registry for passing to subagents (avoids re-discovery) */
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": "16.1.19",
4
+ "version": "16.1.20",
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",
@@ -35,7 +35,7 @@
35
35
  "check": "biome check . && bun run check:types",
36
36
  "check:types": "tsgo -p tsconfig.json --noEmit",
37
37
  "lint": "biome lint .",
38
- "test": "bun test --parallel=4 test src",
38
+ "test": "bun ../../scripts/ci-test-ts.ts coding-agent-heavy --full",
39
39
  "fix": "biome check --write --unsafe . && bun run format-prompts",
40
40
  "fmt": "biome format --write . && bun run format-prompts",
41
41
  "format-prompts": "bun scripts/format-prompts.ts",
@@ -48,17 +48,17 @@
48
48
  "@agentclientprotocol/sdk": "0.25.0",
49
49
  "@babel/parser": "^7.29.7",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "@oh-my-pi/hashline": "16.1.19",
52
- "@oh-my-pi/omp-stats": "16.1.19",
53
- "@oh-my-pi/pi-agent-core": "16.1.19",
54
- "@oh-my-pi/pi-ai": "16.1.19",
55
- "@oh-my-pi/pi-catalog": "16.1.19",
56
- "@oh-my-pi/pi-mnemopi": "16.1.19",
57
- "@oh-my-pi/pi-natives": "16.1.19",
58
- "@oh-my-pi/pi-tui": "16.1.19",
59
- "@oh-my-pi/pi-utils": "16.1.19",
60
- "@oh-my-pi/pi-wire": "16.1.19",
61
- "@oh-my-pi/snapcompact": "16.1.19",
51
+ "@oh-my-pi/hashline": "16.1.20",
52
+ "@oh-my-pi/omp-stats": "16.1.20",
53
+ "@oh-my-pi/pi-agent-core": "16.1.20",
54
+ "@oh-my-pi/pi-ai": "16.1.20",
55
+ "@oh-my-pi/pi-catalog": "16.1.20",
56
+ "@oh-my-pi/pi-mnemopi": "16.1.20",
57
+ "@oh-my-pi/pi-natives": "16.1.20",
58
+ "@oh-my-pi/pi-tui": "16.1.20",
59
+ "@oh-my-pi/pi-utils": "16.1.20",
60
+ "@oh-my-pi/pi-wire": "16.1.20",
61
+ "@oh-my-pi/snapcompact": "16.1.20",
62
62
  "@opentelemetry/api": "^1.9.1",
63
63
  "@opentelemetry/context-async-hooks": "^2.7.1",
64
64
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -21,13 +21,42 @@ import { captureGalleryScreenshots } from "./gallery-screenshot";
21
21
  export const GALLERY_STATES = ["streaming", "progress", "success", "error"] as const;
22
22
  export type GalleryState = (typeof GALLERY_STATES)[number];
23
23
 
24
- const STATE_LABELS: Record<GalleryState, string> = {
24
+ /** User-facing labels printed above each rendered lifecycle state. */
25
+ export const GALLERY_STATE_LABELS: Record<GalleryState, string> = {
25
26
  streaming: "streaming args",
26
27
  progress: "in progress",
27
28
  success: "done",
28
29
  error: "failed",
29
30
  };
30
31
 
32
+ const GALLERY_STATE_ALIASES: Record<string, GalleryState> = {
33
+ streaming: "streaming",
34
+ "streaming args": "streaming",
35
+ progress: "progress",
36
+ "in progress": "progress",
37
+ success: "success",
38
+ done: "success",
39
+ error: "error",
40
+ failed: "error",
41
+ };
42
+
43
+ /** Accepted `--state` tokens, including legacy lifecycle names and displayed labels. */
44
+ export const GALLERY_STATE_TOKENS = Object.keys(GALLERY_STATE_ALIASES);
45
+
46
+ /** Normalize user-provided `--state` tokens to the internal gallery lifecycle states. */
47
+ export function parseGalleryStates(states: readonly string[] | undefined): GalleryState[] | undefined {
48
+ if (!states || states.length === 0) return undefined;
49
+ const parsed: GalleryState[] = [];
50
+ for (const raw of states) {
51
+ const state = GALLERY_STATE_ALIASES[raw.trim().toLowerCase()];
52
+ if (!state) {
53
+ throw new Error(`Invalid --state '${raw}'. Valid values: ${GALLERY_STATE_TOKENS.join(", ")}`);
54
+ }
55
+ if (!parsed.includes(state)) parsed.push(state);
56
+ }
57
+ return parsed;
58
+ }
59
+
31
60
  export interface GalleryCommandArgs {
32
61
  /** Render width in columns (defaults to terminal width, clamped). */
33
62
  width?: number;
@@ -166,7 +195,7 @@ async function renderGallerySections(
166
195
  const heading = fixture.label && fixture.label !== name ? `${name} — ${fixture.label}` : name;
167
196
  const lines: string[] = ["", sectionRule(heading, width)];
168
197
  for (const state of states) {
169
- lines.push("", theme.fg("dim", ` · ${STATE_LABELS[state]}`));
198
+ lines.push("", theme.fg("dim", ` · ${GALLERY_STATE_LABELS[state]}`));
170
199
  try {
171
200
  for (const line of await renderGalleryState(name, fixture, state, width, expanded)) lines.push(line);
172
201
  } catch (err) {
@@ -263,6 +263,11 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
263
263
  ],
264
264
  } satisfies IrcDetails,
265
265
  },
266
+ errorResult: {
267
+ isError: true,
268
+ content: [{ type: "text", text: "IRC inbox failed: message store unavailable." }],
269
+ details: { op: "inbox" } satisfies IrcDetails,
270
+ },
266
271
  },
267
272
 
268
273
  irc_list: {
@@ -309,6 +314,11 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
309
314
  ],
310
315
  } satisfies IrcDetails,
311
316
  },
317
+ errorResult: {
318
+ isError: true,
319
+ content: [{ type: "text", text: "IRC list failed: agent hub is unavailable." }],
320
+ details: { op: "list" } satisfies IrcDetails,
321
+ },
312
322
  },
313
323
 
314
324
  goal: {
@@ -388,19 +398,18 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
388
398
  },
389
399
  errorResult: {
390
400
  isError: true,
391
- content: [{ type: "text", text: "Job cancelled by user." }],
401
+ content: [{ type: "text", text: "1 job failed." }],
392
402
  details: {
393
403
  jobs: [
394
404
  {
395
405
  id: "job_d4",
396
406
  type: "task",
397
- status: "cancelled",
407
+ status: "failed",
398
408
  label: "Refactor the session store to Redis",
399
409
  durationMs: 52_300,
400
- errorText: "Aborted: superseded by goal re-scope.",
410
+ errorText: "Subagent exited 1: Redis connection string is missing.",
401
411
  },
402
412
  ],
403
- cancelled: [{ id: "job_d4", status: "cancelled" }],
404
413
  },
405
414
  },
406
415
  },
@@ -2,7 +2,7 @@
2
2
  * Render every built-in tool's renderer across its lifecycle states.
3
3
  */
4
4
  import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
- import { GALLERY_STATES, type GalleryState, runGalleryCommand } from "../cli/gallery-cli";
5
+ import { GALLERY_STATE_TOKENS, type GalleryState, parseGalleryStates, runGalleryCommand } from "../cli/gallery-cli";
6
6
 
7
7
  export default class Gallery extends Command {
8
8
  static description = "Preview tool renderers across streaming, in-progress, success, and failure states";
@@ -12,7 +12,7 @@ export default class Gallery extends Command {
12
12
  state: Flags.string({
13
13
  char: "s",
14
14
  description: "Render only the given lifecycle state(s)",
15
- options: [...GALLERY_STATES],
15
+ options: GALLERY_STATE_TOKENS,
16
16
  multiple: true,
17
17
  }),
18
18
  width: Flags.integer({ char: "w", description: "Render width in columns" }),
@@ -37,9 +37,17 @@ export default class Gallery extends Command {
37
37
 
38
38
  async run(): Promise<void> {
39
39
  const { flags } = await this.parse(Gallery);
40
+ let states: GalleryState[] | undefined;
41
+ try {
42
+ states = parseGalleryStates(flags.state);
43
+ } catch (err) {
44
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
45
+ process.exitCode = 1;
46
+ return;
47
+ }
40
48
  await runGalleryCommand({
41
49
  tool: flags.tool,
42
- states: flags.state as GalleryState[] | undefined,
50
+ states,
43
51
  width: flags.width,
44
52
  expanded: flags.expanded,
45
53
  plain: flags.plain,
@@ -0,0 +1,87 @@
1
+ import type { ServiceTier } from "@oh-my-pi/pi-ai";
2
+ import type { SubmenuOption } from "./settings-schema";
3
+
4
+ /**
5
+ * Service-tier setting values shared by every "Service Tier" setting. `"none"`
6
+ * is the omit-the-parameter sentinel; the remaining values mirror
7
+ * {@link ServiceTier}.
8
+ */
9
+ export const SERVICE_TIER_SETTING_VALUES = [
10
+ "none",
11
+ "auto",
12
+ "default",
13
+ "flex",
14
+ "scale",
15
+ "priority",
16
+ "openai-only",
17
+ "claude-only",
18
+ ] as const;
19
+
20
+ export type ServiceTierSettingValue = (typeof SERVICE_TIER_SETTING_VALUES)[number];
21
+
22
+ /** Variant value set for scoped service-tier settings (subagent/advisor) that can defer to the main agent. */
23
+ export const SERVICE_TIER_INHERIT_SETTING_VALUES = ["inherit", ...SERVICE_TIER_SETTING_VALUES] as const;
24
+
25
+ export type ServiceTierInheritSettingValue = (typeof SERVICE_TIER_INHERIT_SETTING_VALUES)[number];
26
+
27
+ /** Submenu descriptions shared by the base `serviceTier` setting. */
28
+ export const SERVICE_TIER_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierSettingValue>> = [
29
+ { value: "none", label: "None", description: "Omit service_tier parameter" },
30
+ { value: "auto", label: "Auto", description: "Use provider default tier selection (OpenAI)" },
31
+ { value: "default", label: "Default", description: "Standard priority processing (OpenAI)" },
32
+ { value: "flex", label: "Flex", description: "Flexible capacity tier when available (OpenAI)" },
33
+ { value: "scale", label: "Scale", description: "Scale Tier credits when available (OpenAI)" },
34
+ {
35
+ value: "priority",
36
+ label: "Priority",
37
+ description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)",
38
+ },
39
+ {
40
+ value: "openai-only",
41
+ label: "Priority (OpenAI only)",
42
+ description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere",
43
+ },
44
+ {
45
+ value: "claude-only",
46
+ label: "Priority (Claude only)",
47
+ description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)",
48
+ },
49
+ ];
50
+
51
+ /** Submenu descriptions for inherit-capable service-tier settings. */
52
+ export const SERVICE_TIER_INHERIT_OPTIONS: ReadonlyArray<SubmenuOption<ServiceTierInheritSettingValue>> = [
53
+ { value: "inherit", label: "Inherit", description: "Use the main agent's Service Tier" },
54
+ ...SERVICE_TIER_OPTIONS,
55
+ ];
56
+
57
+ /**
58
+ * Resolve a service-tier setting value to the wire {@link ServiceTier} (or
59
+ * `undefined` to omit). `"inherit"` defers to `inherited`; `"none"` omits.
60
+ */
61
+ export function resolveServiceTierSetting(value: string, inherited: ServiceTier | undefined): ServiceTier | undefined {
62
+ if (value === "inherit") return inherited;
63
+ if (value === "none" || value === "") return undefined;
64
+ return value as ServiceTier;
65
+ }
66
+
67
+ /**
68
+ * Resolve the `serviceTier` *setting value* to stamp onto a subagent's settings
69
+ * snapshot.
70
+ *
71
+ * - A concrete `subagentSetting` (`"none"` or a tier) wins outright.
72
+ * - `"inherit"` defers to the parent's live effective tier when the caller has a
73
+ * live session (`inherited` passed as `ServiceTier | null`, where `null` means
74
+ * the parent explicitly has no tier — e.g. `/fast off`). When no live session
75
+ * is available (`inherited === undefined`, e.g. cold subagent revive) it falls
76
+ * back to the parent's configured `serviceTier` setting so behavior matches a
77
+ * plain settings snapshot.
78
+ */
79
+ export function resolveSubagentServiceTier(
80
+ subagentSetting: string,
81
+ configuredTier: ServiceTierSettingValue,
82
+ inherited: ServiceTier | null | undefined,
83
+ ): ServiceTierSettingValue {
84
+ if (subagentSetting !== "inherit") return subagentSetting as ServiceTierSettingValue;
85
+ if (inherited === undefined) return configuredTier;
86
+ return inherited ?? "none";
87
+ }
@@ -35,6 +35,12 @@ import {
35
35
  } from "../tts/models";
36
36
  import { EDIT_MODES } from "../utils/edit-mode";
37
37
  import { SEARCH_PROVIDER_OPTIONS, SEARCH_PROVIDER_PREFERENCES, type SearchProviderId } from "../web/search/types";
38
+ import {
39
+ SERVICE_TIER_INHERIT_OPTIONS,
40
+ SERVICE_TIER_INHERIT_SETTING_VALUES,
41
+ SERVICE_TIER_OPTIONS,
42
+ SERVICE_TIER_SETTING_VALUES,
43
+ } from "./service-tier";
38
44
 
39
45
  /** Unified settings schema - single source of truth for all settings.
40
46
  *
@@ -1122,7 +1128,7 @@ export const SETTINGS_SCHEMA = {
1122
1128
 
1123
1129
  serviceTier: {
1124
1130
  type: "enum",
1125
- values: ["none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"] as const,
1131
+ values: SERVICE_TIER_SETTING_VALUES,
1126
1132
  default: "none",
1127
1133
  ui: {
1128
1134
  tab: "model",
@@ -1130,28 +1136,36 @@ export const SETTINGS_SCHEMA = {
1130
1136
  label: "Service Tier",
1131
1137
  description:
1132
1138
  'Processing priority hint (none = omit). OpenAI accepts the tier values directly; Anthropic realizes `priority` as `speed: "fast"` on supported Opus models. Scoped values target one family.',
1133
- options: [
1134
- { value: "none", label: "None", description: "Omit service_tier parameter" },
1135
- { value: "auto", label: "Auto", description: "Use provider default tier selection (OpenAI)" },
1136
- { value: "default", label: "Default", description: "Standard priority processing (OpenAI)" },
1137
- { value: "flex", label: "Flex", description: "Flexible capacity tier when available (OpenAI)" },
1138
- { value: "scale", label: "Scale", description: "Scale Tier credits when available (OpenAI)" },
1139
- {
1140
- value: "priority",
1141
- label: "Priority",
1142
- description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)",
1143
- },
1144
- {
1145
- value: "openai-only",
1146
- label: "Priority (OpenAI only)",
1147
- description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere",
1148
- },
1149
- {
1150
- value: "claude-only",
1151
- label: "Priority (Claude only)",
1152
- description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)",
1153
- },
1154
- ],
1139
+ options: SERVICE_TIER_OPTIONS,
1140
+ },
1141
+ },
1142
+
1143
+ serviceTierSubagent: {
1144
+ type: "enum",
1145
+ values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1146
+ default: "inherit",
1147
+ ui: {
1148
+ tab: "model",
1149
+ group: "Sampling",
1150
+ label: "Service Tier - Subagent",
1151
+ description:
1152
+ "Service Tier for spawned task/eval subagents. Inherit = match the main agent's live tier (tracks /fast); pick a value to scope subagents independently.",
1153
+ options: SERVICE_TIER_INHERIT_OPTIONS,
1154
+ },
1155
+ },
1156
+
1157
+ serviceTierAdvisor: {
1158
+ type: "enum",
1159
+ values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1160
+ default: "none",
1161
+ ui: {
1162
+ tab: "model",
1163
+ group: "Sampling",
1164
+ label: "Service Tier - Advisor",
1165
+ description:
1166
+ "Service Tier for the advisor model. None = standard processing; Inherit = match the main agent's live tier; pick a value (e.g. Priority) to run the advisor on a faster serving path.",
1167
+ options: SERVICE_TIER_INHERIT_OPTIONS,
1168
+ condition: "advisorEnabled",
1155
1169
  },
1156
1170
  },
1157
1171
 
@@ -4041,6 +4055,17 @@ export const SETTINGS_SCHEMA = {
4041
4055
  },
4042
4056
 
4043
4057
  // Provider selection
4058
+ "providers.ollama-cloud.maxConcurrency": {
4059
+ type: "number",
4060
+ default: 3,
4061
+ ui: {
4062
+ tab: "providers",
4063
+ group: "Services",
4064
+ label: "Ollama Cloud Max Concurrency",
4065
+ description:
4066
+ "Maximum concurrent Ollama Cloud subagent runs per process; 0 disables the provider-specific limit",
4067
+ },
4068
+ },
4044
4069
  "providers.webSearch": {
4045
4070
  type: "enum",
4046
4071
  values: SEARCH_PROVIDER_PREFERENCES,
@@ -397,6 +397,8 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
397
397
  parentMnemopiSessionState: options.session.getMnemopiSessionState?.(),
398
398
  parentTelemetry: options.session.getTelemetry?.(),
399
399
  parentAgentId: options.session.getAgentId?.() ?? MAIN_AGENT_ID,
400
+ // Live source of truth for `serviceTierSubagent: inherit` (null = explicit none).
401
+ parentServiceTier: options.session.getServiceTier ? (options.session.getServiceTier() ?? null) : undefined,
400
402
  // Deliberately omit parentEvalSessionId: the parent's Python kernel is
401
403
  // blocked on this bridge call, so sharing the eval session would deadlock
402
404
  // (subagent queues behind the parent's in-flight execution, parent waits
package/src/main.ts CHANGED
@@ -140,6 +140,7 @@ const HOST_DEFAULTED_SETTING_PATHS: SettingPath[] = [
140
140
  "advisor.subagents",
141
141
  "advisor.syncBacklog",
142
142
  "advisor.immuneTurns",
143
+ "serviceTierAdvisor",
143
144
  ];
144
145
 
145
146
  const RPC_BACKGROUND_DEFAULTED_SETTING_PATHS: SettingPath[] = [
@@ -340,6 +340,10 @@ export class StdioTransport implements MCPTransport {
340
340
  platform: process.platform,
341
341
  });
342
342
 
343
+ // Spawn in a new session (detached → setsid) so the MCP process tree has
344
+ // no controlling terminal. Otherwise terminal job-control signals (Ctrl+Z
345
+ // SIGTSTP, background-read SIGTTIN) can stop stdio servers such as
346
+ // chrome-devtools-mcp and leave our read loop blocked on silent pipes.
343
347
  this.#process = spawn({
344
348
  cmd: spawnCommand.cmd,
345
349
  cwd,
@@ -348,6 +352,7 @@ export class StdioTransport implements MCPTransport {
348
352
  stdout: "pipe",
349
353
  stderr: "pipe",
350
354
  windowsHide: spawnCommand.windowsHide,
355
+ detached: true,
351
356
  });
352
357
 
353
358
  this.#connected = true;