From 7b05bf80cb2f35142ab5bd1d2e4a19fe795a9a4d Mon Sep 17 00:00:00 2001 From: Gagan Date: Tue, 21 Jul 2026 20:58:53 +0530 Subject: [PATCH] refactor(x): drop limits DI, read setting at default site; async fs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review: remove ITurnLimitsResolver, its bridge and container registration. createTurn now defaults an omitted maxModelCalls straight from the turn-limits config (lazy import, built-in fallback), with no chat/headless distinction in the runtime — the chat UI passes its optional chat override explicitly via the existing sessions:sendMessage maxModelCalls field. turn_limits.ts now uses fs/promises throughout. --- apps/x/apps/main/src/ipc.ts | 4 +- apps/x/apps/renderer/src/App.tsx | 8 +++ .../packages/core/docs/turn-runtime-design.md | 14 ++--- .../core/src/config/turn_limits.test.ts | 48 ++++----------- .../x/packages/core/src/config/turn_limits.ts | 52 +++++++---------- apps/x/packages/core/src/di/container.ts | 3 - .../core/src/runtime/assembly/spawn-agent.ts | 2 +- .../core/src/runtime/turns/bridges/index.ts | 1 - .../bridges/real-turn-limits-resolver.ts | 12 ---- .../core/src/runtime/turns/runtime.test.ts | 58 +++++++++---------- .../core/src/runtime/turns/runtime.ts | 26 +++++---- .../src/runtime/turns/turn-limits-resolver.ts | 8 --- 12 files changed, 91 insertions(+), 145 deletions(-) delete mode 100644 apps/x/packages/core/src/runtime/turns/bridges/real-turn-limits-resolver.ts delete mode 100644 apps/x/packages/core/src/runtime/turns/turn-limits-resolver.ts diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index a2c6b6b2..cd8c3551 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -2561,10 +2561,10 @@ export function setupIpcHandlers() { return { success: true }; }, 'turnLimits:getSettings': async () => { - return loadTurnLimitsSettings(); + return await loadTurnLimitsSettings(); }, 'turnLimits:setSettings': async (_event, args) => { - saveTurnLimitsSettings(args); + await saveTurnLimitsSettings(args); return { success: true }; }, // Embedded browser handlers (WebContentsView + navigation) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 692a055a..58b78d14 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -3021,6 +3021,13 @@ function App() { // via the agent resolver; keep them session-sticky where possible so the // provider prefix cache survives across turns. const reasoningEffort = reasoningEffortByTabRef.current.get(submitTabId) + // The runtime defaults omitted maxModelCalls to the global limit; the + // chat-specific override is the UI's job to pass explicitly. A failed + // settings read just falls back to the global limit. + const chatMaxModelCalls = await window.ipc + .invoke('turnLimits:getSettings', null) + .then((settings) => settings.chatMaxModelCalls) + .catch(() => undefined) const sendConfig = { agent: { agentId, @@ -3039,6 +3046,7 @@ function App() { }, autoPermission: (permissionMode ?? 'manual') === 'auto', ...(reasoningEffort ? { reasoningEffort } : {}), + ...(chatMaxModelCalls !== undefined ? { maxModelCalls: chatMaxModelCalls } : {}), } const userMessageContextFor = (middlePane: Awaited>) => ({ currentDateTime: new Date().toISOString(), diff --git a/apps/x/packages/core/docs/turn-runtime-design.md b/apps/x/packages/core/docs/turn-runtime-design.md index 7a865499..4f8d4e36 100644 --- a/apps/x/packages/core/docs/turn-runtime-design.md +++ b/apps/x/packages/core/docs/turn-runtime-design.md @@ -512,13 +512,13 @@ Rules: - `input` is the user message that defines this turn boundary. - `autoPermission` defaults to `false` before persistence. - `humanAvailable` is required explicitly. -- `maxModelCalls`, when omitted, is resolved at creation from the user's - turn-limit settings (`config/turn_limits.json`) via the injected - `ITurnLimitsResolver`: turns with `humanAvailable: true` use the chat - override when set, otherwise the global limit; headless turns always use - the global limit. Without a resolver (tests) it defaults to `50`. Spawned - sub-agents default to the global limit, which also caps the budget a - parent may grant them. Settings changes affect only newly created turns. +- `maxModelCalls`, when omitted, defaults at creation to the user's global + model-call limit (`config/turn_limits.json`, built-in default `50`). The + runtime makes no chat/headless distinction: the optional chat override is + the chat UI's job — its send path passes an explicit `maxModelCalls`. + Spawned sub-agents default to the global limit, which also caps the + budget a parent may grant them. Settings changes affect only newly + created turns. - Persisted values are fully resolved and immutable. The capability is named `humanAvailable`, not `headless`. `headless` describes diff --git a/apps/x/packages/core/src/config/turn_limits.test.ts b/apps/x/packages/core/src/config/turn_limits.test.ts index 88afffc4..83f19a5a 100644 --- a/apps/x/packages/core/src/config/turn_limits.test.ts +++ b/apps/x/packages/core/src/config/turn_limits.test.ts @@ -45,13 +45,13 @@ async function writeSettings(content: string): Promise { describe("loadTurnLimitsSettings", () => { it("defaults to the built-in limit (50) when no file exists", async () => { const { loadTurnLimitsSettings } = await loadTurnLimits(); - expect(loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); }); it("reads persisted settings", async () => { await writeSettings(JSON.stringify({ maxModelCalls: 60, chatMaxModelCalls: 10 })); const { loadTurnLimitsSettings } = await loadTurnLimits(); - expect(loadTurnLimitsSettings()).toEqual({ + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 60, chatMaxModelCalls: 10, }); @@ -60,7 +60,7 @@ describe("loadTurnLimitsSettings", () => { it("fills a missing global limit from the default", async () => { await writeSettings(JSON.stringify({ chatMaxModelCalls: 5 })); const { loadTurnLimitsSettings } = await loadTurnLimits(); - expect(loadTurnLimitsSettings()).toEqual({ + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50, chatMaxModelCalls: 5, }); @@ -69,21 +69,21 @@ describe("loadTurnLimitsSettings", () => { it("falls back to defaults on a corrupt file", async () => { await writeSettings("{not json"); const { loadTurnLimitsSettings } = await loadTurnLimits(); - expect(loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); }); it("falls back to defaults on out-of-range values", async () => { await writeSettings(JSON.stringify({ maxModelCalls: 5000 })); const { loadTurnLimitsSettings } = await loadTurnLimits(); - expect(loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 50 }); }); }); describe("saveTurnLimitsSettings", () => { it("persists valid settings for the next load", async () => { const { saveTurnLimitsSettings, loadTurnLimitsSettings } = await loadTurnLimits(); - saveTurnLimitsSettings({ maxModelCalls: 80, chatMaxModelCalls: 15 }); - expect(loadTurnLimitsSettings()).toEqual({ + await saveTurnLimitsSettings({ maxModelCalls: 80, chatMaxModelCalls: 15 }); + expect(await loadTurnLimitsSettings()).toEqual({ maxModelCalls: 80, chatMaxModelCalls: 15, }); @@ -91,36 +91,10 @@ describe("saveTurnLimitsSettings", () => { it("rejects out-of-range values", async () => { const { saveTurnLimitsSettings } = await loadTurnLimits(); - expect(() => saveTurnLimitsSettings({ maxModelCalls: 0 })).toThrow(); - expect(() => saveTurnLimitsSettings({ maxModelCalls: 501 })).toThrow(); - expect(() => + await expect(saveTurnLimitsSettings({ maxModelCalls: 0 })).rejects.toThrow(); + await expect(saveTurnLimitsSettings({ maxModelCalls: 501 })).rejects.toThrow(); + await expect( saveTurnLimitsSettings({ maxModelCalls: 20, chatMaxModelCalls: 501 }), - ).toThrow(); - }); -}); - -describe("resolveMaxModelCalls", () => { - it("uses the global limit for headless work", async () => { - await writeSettings(JSON.stringify({ maxModelCalls: 60, chatMaxModelCalls: 10 })); - const { resolveMaxModelCalls } = await loadTurnLimits(); - expect(resolveMaxModelCalls({ humanAvailable: false })).toBe(60); - }); - - it("uses the chat override for interactive turns when set", async () => { - await writeSettings(JSON.stringify({ maxModelCalls: 60, chatMaxModelCalls: 10 })); - const { resolveMaxModelCalls } = await loadTurnLimits(); - expect(resolveMaxModelCalls({ humanAvailable: true })).toBe(10); - }); - - it("falls back to the global limit for chat when no override is set", async () => { - await writeSettings(JSON.stringify({ maxModelCalls: 60 })); - const { resolveMaxModelCalls } = await loadTurnLimits(); - expect(resolveMaxModelCalls({ humanAvailable: true })).toBe(60); - }); - - it("resolves 50 everywhere with no settings file", async () => { - const { resolveMaxModelCalls } = await loadTurnLimits(); - expect(resolveMaxModelCalls({ humanAvailable: true })).toBe(50); - expect(resolveMaxModelCalls({ humanAvailable: false })).toBe(50); + ).rejects.toThrow(); }); }); diff --git a/apps/x/packages/core/src/config/turn_limits.ts b/apps/x/packages/core/src/config/turn_limits.ts index 8f77803e..75af9e81 100644 --- a/apps/x/packages/core/src/config/turn_limits.ts +++ b/apps/x/packages/core/src/config/turn_limits.ts @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'fs/promises'; import path from 'path'; import { TurnLimitsSettingsSchema, @@ -14,41 +14,29 @@ const TURN_LIMITS_CONFIG_PATH = path.join(WorkDir, 'config', 'turn_limits.json') * limit DEFAULT_MAX_MODEL_CALLS, no chat override) when the file is absent * or malformed. */ -export function loadTurnLimitsSettings(): TurnLimitsSettings { +export async function loadTurnLimitsSettings(): Promise { try { - if (fs.existsSync(TURN_LIMITS_CONFIG_PATH)) { - const content = fs.readFileSync(TURN_LIMITS_CONFIG_PATH, 'utf-8'); - const parsed = JSON.parse(content); - return TurnLimitsSettingsSchema.parse({ - ...DEFAULT_TURN_LIMITS_SETTINGS, - ...(parsed && typeof parsed === 'object' ? parsed : {}), - }); - } + const content = await fs.readFile(TURN_LIMITS_CONFIG_PATH, 'utf-8'); + const parsed = JSON.parse(content); + return TurnLimitsSettingsSchema.parse({ + ...DEFAULT_TURN_LIMITS_SETTINGS, + ...(parsed && typeof parsed === 'object' ? parsed : {}), + }); } catch (error) { - console.error('[TurnLimits] Error loading turn limit settings:', error); + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error('[TurnLimits] Error loading turn limit settings:', error); + } + return DEFAULT_TURN_LIMITS_SETTINGS; } - return DEFAULT_TURN_LIMITS_SETTINGS; } -export function saveTurnLimitsSettings(settings: TurnLimitsSettings): void { +export async function saveTurnLimitsSettings( + settings: TurnLimitsSettings, +): Promise { const validated = TurnLimitsSettingsSchema.parse(settings); - const dir = path.dirname(TURN_LIMITS_CONFIG_PATH); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(TURN_LIMITS_CONFIG_PATH, JSON.stringify(validated, null, 2)); -} - -/** - * The effective model-call limit for a new turn, by execution context. - * Interactive chat turns (a human is present) use the chat override when - * set; everything else — headless/knowledge work, sub-agents — uses the - * global limit. - */ -export function resolveMaxModelCalls(context: { humanAvailable: boolean }): number { - const settings = loadTurnLimitsSettings(); - if (context.humanAvailable && settings.chatMaxModelCalls !== undefined) { - return settings.chatMaxModelCalls; - } - return settings.maxModelCalls; + await fs.mkdir(path.dirname(TURN_LIMITS_CONFIG_PATH), { recursive: true }); + await fs.writeFile( + TURN_LIMITS_CONFIG_PATH, + JSON.stringify(validated, null, 2), + ); } diff --git a/apps/x/packages/core/src/di/container.ts b/apps/x/packages/core/src/di/container.ts index c5a9e469..90501f67 100644 --- a/apps/x/packages/core/src/di/container.ts +++ b/apps/x/packages/core/src/di/container.ts @@ -50,8 +50,6 @@ import { RealModelRegistry } from "../runtime/turns/bridges/real-model-registry. import { RealToolRegistry } from "../runtime/turns/bridges/real-tool-registry.js"; import { RealPermissionChecker } from "../runtime/turns/bridges/real-permission-checker.js"; import { RealPermissionClassifier } from "../runtime/turns/bridges/real-permission-classifier.js"; -import { RealTurnLimitsResolver } from "../runtime/turns/bridges/real-turn-limits-resolver.js"; -import type { ITurnLimitsResolver } from "../runtime/turns/turn-limits-resolver.js"; import { FSSessionRepo } from "../runtime/sessions/fs-repo.js"; import type { ISessionRepo } from "../runtime/sessions/repo.js"; import { EmitterSessionBus, type ISessionBus } from "../runtime/sessions/bus.js"; @@ -142,7 +140,6 @@ container.register({ toolRegistry: asFunction(() => new RealToolRegistry()).singleton(), permissionChecker: asFunction(() => new RealPermissionChecker()).singleton(), permissionClassifier: asFunction(() => new RealPermissionClassifier()).singleton(), - turnLimitsResolver: asFunction(() => new RealTurnLimitsResolver()).singleton(), turnRuntime: asClass(TurnRuntime).singleton(), sessionRepo: asClass(FSSessionRepo).singleton(), sessionBus: asClass(EmitterSessionBus).singleton(), diff --git a/apps/x/packages/core/src/runtime/assembly/spawn-agent.ts b/apps/x/packages/core/src/runtime/assembly/spawn-agent.ts index 98e31c1c..2b7ad77d 100644 --- a/apps/x/packages/core/src/runtime/assembly/spawn-agent.ts +++ b/apps/x/packages/core/src/runtime/assembly/spawn-agent.ts @@ -247,7 +247,7 @@ async function resolveServices(): Promise< headlessRunner: await lazyResolve< import("./headless.js").IHeadlessAgentRunner >("headlessAgentRunner"), - globalMaxModelCalls: loadTurnLimitsSettings().maxModelCalls, + globalMaxModelCalls: (await loadTurnLimitsSettings()).maxModelCalls, }; } diff --git a/apps/x/packages/core/src/runtime/turns/bridges/index.ts b/apps/x/packages/core/src/runtime/turns/bridges/index.ts index e93f132e..5927554c 100644 --- a/apps/x/packages/core/src/runtime/turns/bridges/index.ts +++ b/apps/x/packages/core/src/runtime/turns/bridges/index.ts @@ -3,4 +3,3 @@ export * from "./real-model-registry.js"; export * from "./real-permission-checker.js"; export * from "./real-permission-classifier.js"; export * from "./real-tool-registry.js"; -export * from "./real-turn-limits-resolver.js"; diff --git a/apps/x/packages/core/src/runtime/turns/bridges/real-turn-limits-resolver.ts b/apps/x/packages/core/src/runtime/turns/bridges/real-turn-limits-resolver.ts deleted file mode 100644 index 456e6029..00000000 --- a/apps/x/packages/core/src/runtime/turns/bridges/real-turn-limits-resolver.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { resolveMaxModelCalls } from "../../../config/turn_limits.js"; -import type { ITurnLimitsResolver } from "../turn-limits-resolver.js"; - -// Settings-backed limits resolver: reads config/turn_limits.json on every -// resolve so a settings change applies to the next created turn without a -// restart. Turns that already exist keep the limit persisted in their -// turn_created event. -export class RealTurnLimitsResolver implements ITurnLimitsResolver { - resolve(context: { humanAvailable: boolean }): number { - return resolveMaxModelCalls(context); - } -} diff --git a/apps/x/packages/core/src/runtime/turns/runtime.test.ts b/apps/x/packages/core/src/runtime/turns/runtime.test.ts index 5b49e1b0..60afb0e6 100644 --- a/apps/x/packages/core/src/runtime/turns/runtime.test.ts +++ b/apps/x/packages/core/src/runtime/turns/runtime.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { z } from "zod"; import { MODEL_CALL_LIMIT_ERROR_CODE, @@ -40,7 +40,16 @@ import type { SyncRuntimeTool, ToolExecutionContext, } from "./tool-registry.js"; -import type { ITurnLimitsResolver } from "./turn-limits-resolver.js"; + +// createTurn defaults an omitted maxModelCalls from the fs-backed settings +// module; mock it so tests stay hermetic (no real WorkDir reads) and can +// steer the configured global limit. +const turnLimitsMock = vi.hoisted(() => ({ maxModelCalls: 50 })); +vi.mock("../../config/turn_limits.js", () => ({ + loadTurnLimitsSettings: async () => ({ + maxModelCalls: turnLimitsMock.maxModelCalls, + }), +})); // --------------------------------------------------------------------------- // Fixtures @@ -323,7 +332,6 @@ function makeRuntime(opts: { idStart?: number; modelRegistry?: IModelRegistry; toolRegistry?: IToolRegistry; - turnLimitsResolver?: ITurnLimitsResolver; } = {}) { const repo = opts.repo ?? new InMemoryTurnRepo(); const models = new FakeModelRegistry(opts.models ?? []); @@ -345,7 +353,6 @@ function makeRuntime(opts: { lifecycleBus: bus, turnEventBus, usageReporter: usage, - turnLimitsResolver: opts.turnLimitsResolver, }); return { runtime, repo, models, checker, classifier, bus, turnEventBus, usage }; } @@ -3069,13 +3076,6 @@ describe("turn event bus", () => { // --------------------------------------------------------------------------- describe("model-call limit resolution", () => { - // Chat turns (humanAvailable) and headless turns resolve through the - // injected settings-backed resolver; an explicit maxModelCalls always - // wins; without a resolver the built-in default (20) applies. - const contextualResolver: ITurnLimitsResolver = { - resolve: ({ humanAvailable }) => (humanAvailable ? 7 : 42), - }; - async function createdMaxModelCalls( runtime: TurnRuntime, repo: InMemoryTurnRepo, @@ -3087,22 +3087,23 @@ describe("model-call limit resolution", () => { return created.config.maxModelCalls; } - it("resolves an omitted limit by execution context", async () => { - const { runtime, repo } = makeRuntime({ - turnLimitsResolver: contextualResolver, - }); - expect( - await createdMaxModelCalls(runtime, repo, { humanAvailable: true }), - ).toBe(7); - expect( - await createdMaxModelCalls(runtime, repo, { humanAvailable: false }), - ).toBe(42); + it("an omitted limit defaults to the configured global limit", async () => { + turnLimitsMock.maxModelCalls = 42; + try { + const { runtime, repo } = makeRuntime(); + expect( + await createdMaxModelCalls(runtime, repo, { humanAvailable: true }), + ).toBe(42); + expect( + await createdMaxModelCalls(runtime, repo, { humanAvailable: false }), + ).toBe(42); + } finally { + turnLimitsMock.maxModelCalls = 50; + } }); - it("an explicit per-call limit wins over the resolver", async () => { - const { runtime, repo } = makeRuntime({ - turnLimitsResolver: contextualResolver, - }); + it("an explicit per-call limit wins over the setting", async () => { + const { runtime, repo } = makeRuntime(); expect( await createdMaxModelCalls(runtime, repo, { humanAvailable: true, @@ -3110,11 +3111,4 @@ describe("model-call limit resolution", () => { }), ).toBe(3); }); - - it("falls back to the built-in default without a resolver", async () => { - const { runtime, repo } = makeRuntime(); - expect( - await createdMaxModelCalls(runtime, repo, { humanAvailable: true }), - ).toBe(50); - }); }); diff --git a/apps/x/packages/core/src/runtime/turns/runtime.ts b/apps/x/packages/core/src/runtime/turns/runtime.ts index 017efe69..e118f2ec 100644 --- a/apps/x/packages/core/src/runtime/turns/runtime.ts +++ b/apps/x/packages/core/src/runtime/turns/runtime.ts @@ -54,7 +54,6 @@ import type { IPermissionChecker, IPermissionClassifier } from "./permission.js" import type { ITurnRepo } from "./repo.js"; import { HotStream } from "./stream.js"; import type { IToolRegistry, RuntimeTool, SyncRuntimeTool } from "./tool-registry.js"; -import type { ITurnLimitsResolver } from "./turn-limits-resolver.js"; type TEvent = z.infer; @@ -74,8 +73,6 @@ export interface TurnRuntimeDependencies { lifecycleBus: ITurnLifecycleBus; turnEventBus: ITurnEventBus; usageReporter: IUsageReporter; - // Optional: absent (tests) falls back to DEFAULT_MAX_MODEL_CALLS. - turnLimitsResolver?: ITurnLimitsResolver; } // Immutable dependency container: holds no mutable per-turn state. All active @@ -93,7 +90,6 @@ export class TurnRuntime implements ITurnRuntime { private readonly lifecycleBus: ITurnLifecycleBus; private readonly turnEventBus: ITurnEventBus; private readonly usageReporter: IUsageReporter; - private readonly turnLimitsResolver?: ITurnLimitsResolver; constructor({ turnRepo, @@ -108,7 +104,6 @@ export class TurnRuntime implements ITurnRuntime { lifecycleBus, turnEventBus, usageReporter, - turnLimitsResolver, }: TurnRuntimeDependencies) { this.turnRepo = turnRepo; this.idGenerator = idGenerator; @@ -122,7 +117,6 @@ export class TurnRuntime implements ITurnRuntime { this.lifecycleBus = lifecycleBus; this.turnEventBus = turnEventBus; this.usageReporter = usageReporter; - this.turnLimitsResolver = turnLimitsResolver; } async createTurn(input: CreateTurnInput): Promise { @@ -171,10 +165,7 @@ export class TurnRuntime implements ITurnRuntime { humanAvailable: input.config.humanAvailable, maxModelCalls: input.config.maxModelCalls ?? - this.turnLimitsResolver?.resolve({ - humanAvailable: input.config.humanAvailable, - }) ?? - DEFAULT_MAX_MODEL_CALLS, + (await defaultMaxModelCalls()), ...(input.config.reasoningEffort === undefined ? {} : { reasoningEffort: input.config.reasoningEffort }), @@ -1370,6 +1361,21 @@ function parseToolAdditions( return parsed.success ? parsed.data.toolAdditions : undefined; } +// The default budget for turns created without an explicit maxModelCalls: +// the user's configured global limit (config/turn_limits.json). Loaded +// lazily so this module doesn't statically pull in the fs-backed config; +// any load problem falls back to the built-in default. +async function defaultMaxModelCalls(): Promise { + try { + const { loadTurnLimitsSettings } = await import( + "../../config/turn_limits.js" + ); + return (await loadTurnLimitsSettings()).maxModelCalls; + } catch { + return DEFAULT_MAX_MODEL_CALLS; + } +} + function errorMessage(error: unknown): string { const message = error instanceof Error ? error.message : String(error); // Provider API errors carry the actual upstream failure in responseBody diff --git a/apps/x/packages/core/src/runtime/turns/turn-limits-resolver.ts b/apps/x/packages/core/src/runtime/turns/turn-limits-resolver.ts deleted file mode 100644 index 37bbac73..00000000 --- a/apps/x/packages/core/src/runtime/turns/turn-limits-resolver.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Resolves the effective model-call limit for a new turn when the caller -// didn't pass an explicit maxModelCalls. The execution context is the -// humanAvailable flag: true for interactive chat turns, false for -// headless/autonomous work. The real bridge reads the user's settings; -// tests construct TurnRuntime without one and get the built-in default. -export interface ITurnLimitsResolver { - resolve(context: { humanAvailable: boolean }): number; -}