diff --git a/apps/server/src/mcp/McpToolAccess.test.ts b/apps/server/src/mcp/McpToolAccess.test.ts index 6d63287f4eb2a..bb9b177c9f0cd 100644 --- a/apps/server/src/mcp/McpToolAccess.test.ts +++ b/apps/server/src/mcp/McpToolAccess.test.ts @@ -15,6 +15,7 @@ import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import { McpSchema, McpServer, Tool, Toolkit } from "effect/ai"; +import { DispatchModeLimit } from "../orchestration-v2/DispatchModeLimit.ts"; import { OrchestratorProjectionError } from "../orchestration-v2/Orchestrator.ts"; import * as PeerForwarding from "../peer/PeerForwarding.ts"; import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts"; @@ -31,6 +32,22 @@ const backgroundThreadId = ThreadId.make("thread:background"); const oldProviderThreadId = ThreadId.make("thread:old-provider-background"); const archivedThreadId = ThreadId.make("thread:archived-background"); const unknownOwnerThreadId = ThreadId.make("thread:unknown-background-owner"); +const ownChildThreadId = ThreadId.make("thread:own-child"); +const forgedChildThreadId = ThreadId.make("thread:forged-child"); +const planChildThreadId = ThreadId.make("thread:plan-child"); + +const childShell = (threadId: ThreadId, parentThreadId: ThreadId) => ({ + ...liveThreadShell(threadId), + lineage: { + rootThreadId: parentThreadId, + parentThreadId, + relationshipToParent: "subagent" as const, + }, +}); +const appOwnedChildren = new Map([ + [supervisedThreadId, [ownChildThreadId]], + [planThreadId, [planChildThreadId]], +]); const backgroundShell = (threadId: ThreadId, providerInstanceId: ProviderInstanceId) => ({ ...liveThreadShell(threadId, { activeRunId: null }), @@ -63,6 +80,10 @@ const shells = new Map([ pendingBackgroundTasks: [{ taskId: "task:unknown", kind: "subagent" as const }], }, ], + [ownChildThreadId, childShell(ownChildThreadId, supervisedThreadId)], + // Says it is the supervised thread's child, but that thread never delegated it. + [forgedChildThreadId, childShell(forgedChildThreadId, supervisedThreadId)], + [planChildThreadId, childShell(planChildThreadId, planThreadId)], ]); const threadCaller = (threadId: ThreadId): McpInvocationContext.McpInvocationScope => ({ @@ -121,6 +142,10 @@ const ProbeToolkit = Toolkit.make( ...probe, parameters: Schema.Struct({ threadId: Schema.optional(ThreadId) }), }), + Tool.make("writes_own_children", { + ...probe, + parameters: Schema.Struct({ threadId: Schema.optional(ThreadId) }), + }), Tool.make("starts_threads", { ...probe, parameters: Schema.Struct({ @@ -141,6 +166,16 @@ const probeHandlers: McpToolAccess.Handlers = { (input) => [input.threadId], () => ran, ), + // Answers with the modes the orchestrator rechecks under its lock. + writes_own_children: McpToolAccess.writesThreads( + (input) => [input.threadId], + () => + Effect.gen(function* () { + const limit = yield* DispatchModeLimit; + return { ran: `${limit?.runtimeMode}/${limit?.interactionMode}` }; + }), + { ownChildren: "at-their-runtime-mode" }, + ), starts_threads: McpToolAccess.startsThreads( (input) => input, (_, modes) => Effect.succeed({ ran: `${modes.runtimeMode}/${modes.interactionMode}` }), @@ -184,6 +219,7 @@ export const refusedAtCompileTime = () => { writes: unchecked, writes_with_capability: unchecked, writes_threads: unchecked, + writes_own_children: unchecked, starts_threads: unchecked, writes_environment: unchecked, }), @@ -300,6 +336,27 @@ it.effect.each([ ], ["writes_threads", supervised, { threadId: "thread:missing" }, "ran"], + // A thread moves and resumes its own delegated children at the runtime mode they already have. + ["writes_own_children", supervised, { threadId: ownChildThreadId }, "full-access/default"], + [ + "writes_own_children", + plan, + { threadId: planChildThreadId }, + "interaction_mode_escalation_denied", + ], + [ + "writes_own_children", + supervised, + { threadId: forgedChildThreadId }, + "runtime_mode_escalation_denied", + ], + [ + "writes_own_children", + supervisedClient, + { threadId: ownChildThreadId }, + "runtime_mode_escalation_denied", + ], + ["starts_threads", supervised, {}, "approval-required/default"], ["starts_threads", supervised, { runtimeMode: "full-access" }, "runtime_mode_escalation_denied"], ["starts_threads", plan, {}, "approval-required/plan"], @@ -322,6 +379,8 @@ it.effect.each([ probeServer( Layer.mock(ThreadManagement.ThreadManagementService)({ getThreadShell: (threadId) => Effect.succeed(shells.get(threadId) ?? null), + listAppOwnedChildThreadIds: (threadId) => + Effect.succeed(appOwnedChildren.get(threadId) ?? []), }), ), ), diff --git a/apps/server/src/mcp/McpToolAccess.ts b/apps/server/src/mcp/McpToolAccess.ts index f96a38d6d7341..ec8997a15eeaf 100644 --- a/apps/server/src/mcp/McpToolAccess.ts +++ b/apps/server/src/mcp/McpToolAccess.ts @@ -23,12 +23,17 @@ import { import * as PeerForwarding from "../peer/PeerForwarding.ts"; import { assertNotLinked, assertSameLink, callerLinkOrigin } from "./linkOrigin.ts"; import * as McpInvocationContext from "./McpInvocationContext.ts"; -import { resolveInteractionMode, resolveRuntimeMode } from "./delegatedTaskModes.ts"; +import { + resolveInteractionMode, + resolveRuntimeMode, + runtimeModeRank, +} from "./delegatedTaskModes.ts"; import { assertFullAccess, assertLiveCaller, assertTargetWithinLimits, type Caller, + isOwnDelegatedChild, loadCaller, readCaller, unavailable, @@ -197,25 +202,50 @@ export const writesWithCapability = (handle: (params: P) => Effect.E export const writesThreads = ( threads: (params: P) => ReadonlyArray, handle: (params: P) => Effect.Effect, + options?: { + /** + * Whether the caller may act on its own delegated children whose runtime + * mode is above its own, at that mode, as moving one to another account + * and resuming it need. Interaction modes stay within the caller's. + */ + readonly ownChildren?: "at-their-runtime-mode"; + }, ) => declare( (params: P) => Effect.gen(function* () { const caller = yield* writingCaller; + let runtimeMode = caller.limits.runtimeMode; for (const threadId of threads(params)) { if (threadId === undefined || threadId === caller.scope.thread?.threadId) continue; const target = yield* caller.threads .getThreadShell(threadId) .pipe(Effect.mapError(unavailable)); if (target !== null && target.deletedAt === null) { - yield* assertTargetWithinLimits(caller.limits, target); + const ownChild = + options?.ownChildren === "at-their-runtime-mode" && + runtimeModeRank(target.runtimeMode) > runtimeModeRank(caller.limits.runtimeMode) && + (yield* isOwnDelegatedChild(caller.threads, caller.caller?.id, target)); + if (ownChild && runtimeModeRank(target.runtimeMode) > runtimeModeRank(runtimeMode)) + runtimeMode = target.runtimeMode; + yield* assertTargetWithinLimits( + ownChild ? { ...caller.limits, runtimeMode } : caller.limits, + target, + ); yield* assertSameLink(caller, target); } } // Modes can rise after this check; the orchestrator rechecks under lock and records refusal before handlers wrap errors. const refused = yield* Ref.make(undefined); return yield* handle(params).pipe( - Effect.provideService(DispatchModeLimit, { ...caller.limits, refused }), + Effect.provideService(DispatchModeLimit, { + ...caller.limits, + runtimeMode, + ...(runtimeMode === caller.limits.runtimeMode + ? {} + : { ownChildRuntimeMode: runtimeMode }), + refused, + }), Effect.catch((error) => Effect.flatMap(Ref.get(refused), (refusal) => Effect.fail( diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index e954ae7380935..33f6bbfe0f908 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -3192,8 +3192,18 @@ const make = Effect.gen(function* () { } satisfies OrchestratorMcpThreadReadResult; }), sendToThread: Effect.fn("OrchestratorMcpService.sendToThread")(function* (scope, input) { - const { parent, limits, target } = yield* loadScopedThread(scope, input.threadId, ["runs"]); + const { + parent, + limits: callerLimits, + target, + } = yield* loadScopedThread(scope, input.threadId, ["runs"]); yield* assertLiveCallerForOtherThread(scope, parent, target); + // The declaration lifts only the runtime limit, and only to its own child's preflight mode. + const ownChildRuntimeMode = (yield* DispatchModeLimit)?.ownChildRuntimeMode; + const limits = + ownChildRuntimeMode === undefined + ? callerLimits + : { ...callerLimits, runtimeMode: ownChildRuntimeMode }; yield* resolveRuntimeMode(limits.runtimeMode, target.thread.runtimeMode); yield* resolveInteractionMode(limits.interactionMode, target.thread.interactionMode); diff --git a/apps/server/src/mcp/threadAccess.ts b/apps/server/src/mcp/threadAccess.ts index 48d972c5be2fd..42152fb482f66 100644 --- a/apps/server/src/mcp/threadAccess.ts +++ b/apps/server/src/mcp/threadAccess.ts @@ -121,6 +121,24 @@ export const assertTargetWithinLimits = ( Effect.asVoid, ); +/** A thread the calling thread itself delegated here, as `delegate_task` records it on the caller. */ +export const isOwnDelegatedChild = Effect.fn("mcp.isOwnDelegatedChild")(function* ( + threads: Pick, + callerThreadId: ThreadId | undefined, + target: Pick, +) { + if ( + callerThreadId === undefined || + target.lineage.parentThreadId !== callerThreadId || + target.lineage.relationshipToParent !== "subagent" + ) + return false; + const children = yield* threads + .listAppOwnedChildThreadIds(callerThreadId) + .pipe(Effect.mapError(unavailable)); + return children.includes(target.id); +}); + /** Background subagents retain credentials until provider exit; archived threads and other instances cannot act. */ export const isLiveCallerShell = ( shell: Pick< diff --git a/apps/server/src/mcp/toolkits/attachment/handlers.ts b/apps/server/src/mcp/toolkits/attachment/handlers.ts index 8d72943f337ae..c2b2b6118416f 100644 --- a/apps/server/src/mcp/toolkits/attachment/handlers.ts +++ b/apps/server/src/mcp/toolkits/attachment/handlers.ts @@ -125,6 +125,7 @@ export const layer = McpToolAccess.toLayer( status: result.run.status, }; }), + { ownChildren: "at-their-runtime-mode" }, ), }; }), diff --git a/apps/server/src/mcp/toolkits/attachment/tools.ts b/apps/server/src/mcp/toolkits/attachment/tools.ts index f9965850ec8c8..98b3a9f3cfcf0 100644 --- a/apps/server/src/mcp/toolkits/attachment/tools.ts +++ b/apps/server/src/mcp/toolkits/attachment/tools.ts @@ -48,7 +48,7 @@ const AttachmentSendTool = Tool.make("t3_thread_send_attachments", { ...shared, dependencies: [...shared.dependencies, DocumentText.DocumentText], description: - "Send uploaded attachments as a new message to a thread. No retry key. Target modes cannot exceed yours. Success does not mean the provider read them or finished.", + "Send uploaded attachments to a thread within your modes, or your child at its runtime mode. No retry key. Success does not mean the provider read them or finished.", parameters: Schema.Struct({ threadId: Schema.optional(ThreadId), message: Schema.optional(Schema.String.check(Schema.isMaxLength(120000))), diff --git a/apps/server/src/mcp/toolkits/core.test.ts b/apps/server/src/mcp/toolkits/core.test.ts index 5e80cf41b36b5..9856a722f006c 100644 --- a/apps/server/src/mcp/toolkits/core.test.ts +++ b/apps/server/src/mcp/toolkits/core.test.ts @@ -2,12 +2,13 @@ import * as AssetSigner from "../../assets/AssetSigner.ts"; import * as DocumentText from "../../provider/DocumentText.ts"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { expect, it } from "@effect/vitest"; +import { describe, expect, it } from "@effect/vitest"; import { DEFAULT_SERVER_SETTINGS, ChatImageAttachment, CommandId, EnvironmentId, + MessageId, NodeId, OrchestratorMcpThreadReadResult, ProjectId, @@ -40,6 +41,12 @@ import { } from "../../orchestration-v2/Orchestrator.ts"; import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; +import { CodexProviderCapabilitiesV2 } from "../../orchestration-v2/Adapters/CodexAdapterV2.ts"; +import * as CommandReceiptStore from "../../orchestration-v2/CommandReceiptStore.ts"; +import * as Orchestrator from "../../orchestration-v2/Orchestrator.ts"; +import * as ProviderReplayHarness from "../../orchestration-v2/testkit/ProviderReplayHarness.ts"; +import * as SqlitePersistence from "../../persistence/Sqlite.ts"; +import * as ProviderRegistryMock from "../../provider/testUtils/providerRegistryMock.ts"; import * as ServerConfig from "../../config.ts"; import * as ProviderAdapterRegistry from "../../orchestration-v2/ProviderAdapterRegistry.ts"; import * as ThreadLaunchService from "../../orchestration-v2/ThreadLaunchService.ts"; @@ -1040,6 +1047,182 @@ it.effect.each(["t3_thread_interrupt", "t3_workflow_stop"])( ), ); +describe("a thread's own delegated child above its modes", () => { + const codex = ProviderDriverKind.make("codex"); + const accountA = ProviderInstanceId.make("account-a"); + const accountB = ProviderInstanceId.make("account-b"); + const parentId = ThreadId.make("thread:own-child-parent"); + const layerDatabase = SqlitePersistence.layerMemory; + const layerSettings = ServerSettings.layerTest({ + providerInstances: { + [accountA]: { driver: codex, enabled: true }, + [accountB]: { driver: codex, enabled: true }, + }, + }); + const layerAdapters = ProviderAdapterRegistry.layerFromAdapters( + [accountA, accountB].map((instanceId) => ({ + instanceId, + driver: codex, + getCapabilities: () => Effect.succeed(CodexProviderCapabilitiesV2), + planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" as const }), + openSession: () => Effect.die("No provider process runs in these tests"), + })), + ); + const layerOrchestrator = ProviderReplayHarness.layerWithRegistry( + { name: "mcp-own-child" }, + layerAdapters, + { databaseLayer: layerDatabase, runEffectWorker: false }, + ); + const layerThreads = ThreadManagement.layer.pipe( + Layer.provide(layerOrchestrator), + Layer.provide(layerSettings), + Layer.provide(CommandReceiptStore.layer.pipe(Layer.provide(layerDatabase))), + ); + const layerProviders = ProviderRegistryMock.layer( + [accountA, accountB].map((instanceId) => ({ + instanceId, + driver: codex, + enabled: true, + installed: true, + version: "test", + status: "ready" as const, + auth: { status: "authenticated" as const }, + checkedAt: "2026-11-03T00:00:00.000Z", + models: [{ slug: "gpt-5", name: "gpt-5", isCustom: false, capabilities: null }], + slashCommands: [], + skills: [], + })), + ); + const layerToolkits = Layer.mergeAll(layerThreadToolkit, layerOrchestratorToolkit).pipe( + Layer.provideMerge(McpHttpServer.layerInProcessMcpServer), + Layer.provide( + Layer.mergeAll( + layerSettings, + layerAdapters, + layerProviders, + Layer.mock(ProjectService.ProjectService)({}), + Layer.mock(SecretRequests.SecretRequests)({}), + Layer.mock(RemoteDelegation.RemoteDelegation)({}), + Layer.mock(ScheduledTaskService.ScheduledTaskService)({}), + Layer.mock(PeerForwarding.PeerForwarding)({}), + Layer.mock(PeerLinkRequests.PeerLinkRequests)({}), + Layer.mock(PeerLinks.PeerLinks)({}), + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ); + + const callAsParent = (name: string, args: Record) => + McpServer.McpServer.pipe( + Effect.flatMap((server) => server.callTool({ name, arguments: args })), + Effect.provideService(McpInvocationContext.McpInvocationContext, { + ...scope, + thread: { + threadId: parentId, + providerSessionId: "own-child", + providerInstanceId: accountA, + }, + }), + Effect.provideService(McpSchema.McpServerClient, client), + ); + + it.effect( + "an Auto parent moves its stopped Full-access child to another account and resumes it", + () => + Effect.gen(function* () { + const orchestrator = yield* Orchestrator.OrchestratorV2; + let commands = 0; + const commandId = () => CommandId.make(`own-child:${commands++}`); + yield* orchestrator.dispatch({ + type: "thread.create", + commandId: commandId(), + threadId: parentId, + projectId: ProjectId.make("project:own-child"), + title: "parent", + modelSelection: { instanceId: accountA, model: "gpt-5" }, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + createdBy: "user", + creationSource: "web", + }); + yield* orchestrator.dispatch({ + type: "message.dispatch", + commandId: commandId(), + threadId: parentId, + messageId: MessageId.make("message:own-child-parent"), + text: "Coordinate the child.", + attachments: [], + dispatchMode: { type: "start_immediately" }, + createdBy: "user", + creationSource: "web", + }); + const run = (yield* orchestrator.getThreadProjection(parentId)).runs.at(-1)!; + yield* orchestrator.dispatch({ + type: "delegated_task.request", + commandId: commandId(), + parentThreadId: parentId, + parentRunId: run.id, + parentNodeId: run.rootNodeId!, + task: "Keep the delegated context.", + modelSelection: { instanceId: accountA, model: "gpt-5" }, + runtimeMode: "full-access", + interactionMode: "default", + createdBy: "agent", + creationSource: "mcp", + }); + const childId = (yield* orchestrator.getThreadProjection(parentId)).subagents[0]! + .childThreadId!; + yield* orchestrator.dispatch({ + type: "thread.stop", + commandId: commandId(), + threadId: childId, + reason: "Account A reached its usage limit.", + }); + yield* orchestrator.dispatch({ + type: "thread.runtime-mode.set", + commandId: commandId(), + threadId: parentId, + runtimeMode: "auto", + }); + + const moved = yield* callAsParent("t3_thread_configure", { + threadId: childId, + modelSelection: { instanceId: accountB, model: "gpt-5" }, + }); + expect(moved.isError).toBe(false); + const resumed = yield* callAsParent("t3_thread_send", { + threadId: childId, + message: "Go.", + }); + expect(resumed.isError).toBe(false); + const child = yield* orchestrator.getThreadProjection(childId); + expect(child.thread).toMatchObject({ + providerInstanceId: accountB, + runtimeMode: "full-access", + interactionMode: "default", + }); + expect(child.runs.at(-1)?.modelSelection.instanceId).toBe(accountB); + }).pipe( + Effect.provide( + layerToolkits.pipe( + Layer.provideMerge( + Layer.mergeAll( + layerOrchestrator, + layerThreads, + ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-own-child-" }).pipe( + Layer.provideMerge(NodeServices.layer), + ), + ), + ), + ), + ), + ), + ); +}); + it.effect("discovers workflow stop targets and requires orchestration capability", () => { const threadId = ThreadId.make("workflow-parent"); const shell = McpToolAccessTestkit.liveThreadShell(threadId); diff --git a/apps/server/src/mcp/toolkits/orchestrator/handlers.ts b/apps/server/src/mcp/toolkits/orchestrator/handlers.ts index dfa1ad58545ee..ed2b874baa179 100644 --- a/apps/server/src/mcp/toolkits/orchestrator/handlers.ts +++ b/apps/server/src/mcp/toolkits/orchestrator/handlers.ts @@ -182,6 +182,7 @@ const handlers = { const service = yield* OrchestratorMcpService.OrchestratorMcpService; return yield* service.sendToThread(scope, input); }), + { ownChildren: "at-their-runtime-mode" }, ), // There the thread's parent is here, so only this side can make a follow-up a task. (input, environmentId, sent) => diff --git a/apps/server/src/mcp/toolkits/orchestrator/tools.ts b/apps/server/src/mcp/toolkits/orchestrator/tools.ts index d85786c9f5c17..eae4870ee6c24 100644 --- a/apps/server/src/mcp/toolkits/orchestrator/tools.ts +++ b/apps/server/src/mcp/toolkits/orchestrator/tools.ts @@ -277,7 +277,7 @@ const ThreadUpdateTool = Tool.make("t3_thread_update", { const ThreadSendTool = Tool.make("t3_thread_send", { description: - "Send a message to any thread. The target cannot run with broader modes than yours. `mode:'auto'` starts an idle thread, steers a running turn, or queues. `queue`: new turn. `steer`: mid-turn update. `restart`: interrupt and restart. A send to a finished child thread starts a fresh task and returns its `taskId`. In your reply, name it as its `link` verbatim.", + "Send to any thread within your modes, or your child at its runtime mode. `mode:'auto'` starts an idle thread, steers a running turn, or queues. `queue`: new turn. `steer`: mid-turn update. `restart`: interrupt and restart. A send to a finished child thread starts a fresh task and returns its `taskId`. In your reply, name it as its `link` verbatim.", parameters: OrchestratorMcpThreadSendInput, success: OrchestratorMcpThreadSendResult, failure: OrchestratorMcpFailure, diff --git a/apps/server/src/mcp/toolkits/thread/handlers.ts b/apps/server/src/mcp/toolkits/thread/handlers.ts index 62023dd986b55..582e0eaf68ba6 100644 --- a/apps/server/src/mcp/toolkits/thread/handlers.ts +++ b/apps/server/src/mcp/toolkits/thread/handlers.ts @@ -88,7 +88,8 @@ const required = (value: A | undefined, name: string) => : Effect.succeed(value); const writesThread =

( handle: (params: P) => Effect.Effect, -) => McpToolAccess.writesThreads((params: P) => [params.threadId], handle); + options?: Parameters[2], +) => McpToolAccess.writesThreads((params: P) => [params.threadId], handle, options); export const layer = McpToolAccess.toLayer(ThreadToolkit, { run_scheduled_task_now: McpToolAccess.writesEnvironment((input) => @@ -212,19 +213,26 @@ export const layer = McpToolAccess.toLayer(ThreadToolkit, { }; }), ), - writesThread((input) => - Effect.gen(function* () { - const modelSelection = yield* required(input.modelSelection, "modelSelection"); - const { - threads, - projection: { thread }, - } = yield* readThread(input.threadId); - const type = modelSelectionCommandType(thread.providerInstanceId, modelSelection); - const result = yield* threads - .dispatch({ type, threadId: thread.id, commandId: yield* newCommandId(), modelSelection }) - .pipe(Effect.mapError(dispatchFailure)); - return { sequence: result.sequence }; - }), + writesThread( + (input) => + Effect.gen(function* () { + const modelSelection = yield* required(input.modelSelection, "modelSelection"); + const { + threads, + projection: { thread }, + } = yield* readThread(input.threadId); + const type = modelSelectionCommandType(thread.providerInstanceId, modelSelection); + const result = yield* threads + .dispatch({ + type, + threadId: thread.id, + commandId: yield* newCommandId(), + modelSelection, + }) + .pipe(Effect.mapError(dispatchFailure)); + return { sequence: result.sequence }; + }), + { ownChildren: "at-their-runtime-mode" }, ), ), t3_pending_request: McpToolAccess.either( diff --git a/apps/server/src/orchestration-v2/DispatchModeLimit.ts b/apps/server/src/orchestration-v2/DispatchModeLimit.ts index 627806347c5ff..0dbeb5bd8d1bf 100644 --- a/apps/server/src/orchestration-v2/DispatchModeLimit.ts +++ b/apps/server/src/orchestration-v2/DispatchModeLimit.ts @@ -21,6 +21,8 @@ export interface DispatchModeRefusal extends DispatchModes { /** Recheck target modes under the command lock; the user can raise them after preflight. */ export interface DispatchModeLimitValue extends DispatchModes { readonly refused?: Ref.Ref; + /** Set when `runtimeMode` was lifted above the caller's to its own delegated child's preflight mode. */ + readonly ownChildRuntimeMode?: RuntimeMode; } export const DispatchModeLimit = Context.Reference( diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json index 2ce96a5c4180c..234d09a66eef8 100644 --- a/apps/server/src/provider/agentContextBudget.json +++ b/apps/server/src/provider/agentContextBudget.json @@ -1,5 +1,5 @@ { "claudeSystemAppendBytes": 5597, "codexAdditionalContextBytes": 7327, - "mcpToolListJsonBytes": 48099 + "mcpToolListJsonBytes": 48091 }