diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index d5b3ab6ac67..354e5b027c2 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -62,6 +62,7 @@ type AttemptSpawnWorkspaceHoisted = { ensureGlobalUndiciEnvProxyDispatcherMock: UnknownMock; ensureGlobalUndiciStreamTimeoutsMock: UnknownMock; buildEmbeddedMessageActionDiscoveryInputMock: UnknownMock; + createOpenClawCodingToolsMock: UnknownMock; subscribeEmbeddedPiSessionMock: Mock; acquireSessionWriteLockMock: Mock; installToolResultContextGuardMock: UnknownMock; @@ -121,6 +122,7 @@ const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => { const ensureGlobalUndiciEnvProxyDispatcherMock = vi.fn(); const ensureGlobalUndiciStreamTimeoutsMock = vi.fn(); const buildEmbeddedMessageActionDiscoveryInputMock = vi.fn((params: unknown) => params); + const createOpenClawCodingToolsMock = vi.fn(() => []); const installToolResultContextGuardMock = vi.fn(() => () => {}); const installContextEngineLoopHookMock = vi.fn(() => () => {}); const flushPendingToolResultsAfterIdleMock = vi.fn(async () => {}); @@ -170,6 +172,7 @@ const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => { ensureGlobalUndiciEnvProxyDispatcherMock, ensureGlobalUndiciStreamTimeoutsMock, buildEmbeddedMessageActionDiscoveryInputMock, + createOpenClawCodingToolsMock, subscribeEmbeddedPiSessionMock, acquireSessionWriteLockMock, installToolResultContextGuardMock, @@ -427,26 +430,8 @@ vi.mock("../../cache-trace.js", () => ({ })); vi.mock("../../pi-tools.js", () => ({ - createOpenClawCodingTools: (options?: { workspaceDir?: string; spawnWorkspaceDir?: string }) => [ - { - name: "sessions_spawn", - execute: async ( - _callId: string, - input: { task?: string }, - _session?: unknown, - _abortSignal?: unknown, - _ctx?: unknown, - ) => - await hoisted.spawnSubagentDirectMock( - { - task: input.task ?? "", - }, - { - workspaceDir: options?.spawnWorkspaceDir ?? options?.workspaceDir, - }, - ), - }, - ], + createOpenClawCodingTools: (options?: { workspaceDir?: string; spawnWorkspaceDir?: string }) => + hoisted.createOpenClawCodingToolsMock(options), resolveToolLoopDetectionConfig: () => undefined, })); @@ -526,6 +511,9 @@ vi.mock("../../tool-call-id.js", async (importOriginal) => { }); vi.mock("../../tool-fs-policy.js", () => ({ + createToolFsPolicy: (params: { workspaceOnly?: boolean }) => ({ + workspaceOnly: params.workspaceOnly === true, + }), resolveEffectiveToolFsWorkspaceOnly: () => false, })); @@ -767,6 +755,34 @@ export function resetEmbeddedAttemptHarness( hoisted.buildEmbeddedMessageActionDiscoveryInputMock .mockReset() .mockImplementation((params) => params); + hoisted.createOpenClawCodingToolsMock.mockReset().mockImplementation((...args: unknown[]) => { + const options = args[0] as + | { + workspaceDir?: string; + spawnWorkspaceDir?: string; + } + | undefined; + return [ + { + name: "sessions_spawn", + execute: async ( + _callId: string, + input: { task?: string }, + _session?: unknown, + _abortSignal?: unknown, + _ctx?: unknown, + ) => + await hoisted.spawnSubagentDirectMock( + { + task: input.task ?? "", + }, + { + workspaceDir: options?.spawnWorkspaceDir ?? options?.workspaceDir, + }, + ), + }, + ]; + }); hoisted.subscribeEmbeddedPiSessionMock .mockReset() .mockImplementation(() => createSubscriptionMock()); diff --git a/src/agents/pi-embedded-runner/run/attempt.tools-allow-regression.test.ts b/src/agents/pi-embedded-runner/run/attempt.tools-allow-regression.test.ts new file mode 100644 index 00000000000..0cd18a83fb8 --- /dev/null +++ b/src/agents/pi-embedded-runner/run/attempt.tools-allow-regression.test.ts @@ -0,0 +1,59 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + cleanupTempPaths, + createContextEngineAttemptRunner, + getHoisted, + resetEmbeddedAttemptHarness, +} from "./attempt.spawn-workspace.test-support.js"; + +describe("runEmbeddedAttempt toolsAllow startup cost", () => { + const tempPaths: string[] = []; + + beforeEach(() => { + resetEmbeddedAttemptHarness(); + }); + + afterEach(async () => { + await cleanupTempPaths(tempPaths); + }); + + it("keeps plugin-only allowlists on the shared tool policy path", async () => { + const hoisted = getHoisted(); + hoisted.createOpenClawCodingToolsMock.mockReturnValue([ + { + name: "memory_search", + description: "search memory", + parameters: { type: "object", properties: {} }, + execute: async () => "ok", + }, + { + name: "plugin_extra", + description: "extra plugin tool", + parameters: { type: "object", properties: {} }, + execute: async () => "ok", + }, + ]); + + await createContextEngineAttemptRunner({ + contextEngine: { + assemble: async ({ messages }) => ({ messages, estimatedTokens: 1 }), + }, + attemptOverrides: { + toolsAllow: ["memory_search"], + }, + sessionKey: "agent:main:main", + tempPaths, + }); + + expect(hoisted.createOpenClawCodingToolsMock).toHaveBeenCalledWith( + expect.objectContaining({ + includeCoreTools: false, + runtimeToolAllowlist: ["memory_search"], + }), + ); + const createSessionOptions = hoisted.createAgentSessionMock.mock.calls[0]?.[0] as + | { customTools?: { name: string }[] } + | undefined; + expect(createSessionOptions?.customTools?.map((tool) => tool.name)).toEqual(["memory_search"]); + }); +}); diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index dae609bc3dc..f5cd0bb52c5 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -496,6 +496,54 @@ export function applyEmbeddedAttemptToolsAllow( return tools.filter((tool) => allowSet.has(normalizeToolName(tool.name))); } +const CORE_CODING_TOOL_ALLOWLIST_NAMES = new Set([ + "agents_list", + "apply_patch", + "bash", + "canvas", + "cron", + "edit", + "exec", + "gateway", + "heartbeat_response", + "image", + "image_generate", + "message", + "music_generate", + "nodes", + "pdf", + "read", + "session_status", + "sessions_history", + "sessions_list", + "sessions_send", + "sessions_spawn", + "sessions_yield", + "subagents", + "tts", + "update_plan", + "video_generate", + "web_fetch", + "web_search", + "write", +]); + +function shouldBuildCoreCodingToolsForAllowlist(toolsAllow?: string[]): boolean { + if (!toolsAllow || toolsAllow.length === 0) { + return true; + } + return toolsAllow.some((toolName) => { + const normalized = normalizeToolName(toolName); + return ( + normalized === "*" || + normalized.startsWith("group:") || + normalized === "bundle-mcp" || + normalized.includes(TOOL_NAME_SEPARATOR) || + CORE_CODING_TOOL_ALLOWLIST_NAMES.has(normalized) + ); + }); +} + export function normalizeMessagesForLlmBoundary(messages: AgentMessage[]): AgentMessage[] { const normalized = stripToolResultDetails(normalizeAssistantReplayContent(messages)); return stripRuntimeContextCustomMessages(normalized); @@ -696,6 +744,10 @@ export async function runEmbeddedAttempt( config: params.config, agentId: params.agentId, }); + const effectiveFsWorkspaceOnly = resolveAttemptFsWorkspaceOnly({ + config: params.config, + sessionAgentId, + }); prepStages.mark("workspace-sandbox"); let restoreSkillEnv: (() => void) | undefined; @@ -833,6 +885,7 @@ export async function runEmbeddedAttempt( currentChannelId: params.currentChannelId, currentThreadTs: params.currentThreadTs, currentMessageId: params.currentMessageId, + includeCoreTools: shouldBuildCoreCodingToolsForAllowlist(params.toolsAllow), replyToMode: params.replyToMode, hasRepliedRef: params.hasRepliedRef, modelHasVision: params.model.input?.includes("image") ?? false, @@ -942,10 +995,6 @@ export async function runEmbeddedAttempt( config: params.config, agentId: params.agentId, }); - const effectiveFsWorkspaceOnly = resolveAttemptFsWorkspaceOnly({ - config: params.config, - sessionAgentId, - }); // Track sessions_yield tool invocation (callback pattern, like clientToolCallDetected) let yieldDetected = false; let yieldMessage: string | null = null; diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 0d09f5ebd3a..48622dd0642 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -22,6 +22,7 @@ import { listChannelAgentTools } from "./channel-tools.js"; import { shouldSuppressManagedWebSearchTool } from "./codex-native-web-search.js"; import { resolveImageSanitizationLimits } from "./image-sanitization.js"; import type { ModelAuthMode } from "./model-auth.js"; +import { resolveOpenClawPluginToolsForOptions } from "./openclaw-plugin-tools.js"; import { createOpenClawTools } from "./openclaw-tools.js"; import { wrapToolWithAbortSignal } from "./pi-tools.abort.js"; import { wrapToolWithBeforeToolCallHook } from "./pi-tools.before-tool-call.js"; @@ -352,6 +353,8 @@ export function createOpenClawCodingTools(options?: { enableHeartbeatTool?: boolean; /** Keep the heartbeat response tool available even when the selected profile omits it. */ forceHeartbeatTool?: boolean; + /** If false, build plugin tools only while preserving the shared policy pipeline. */ + includeCoreTools?: boolean; /** Whether the sender is an owner (required for owner-only tools). */ senderIsOwner?: boolean; /** @@ -467,6 +470,7 @@ export function createOpenClawCodingTools(options?: { const sandboxFsBridge = sandbox?.fsBridge; const allowWorkspaceWrites = sandbox?.workspaceAccess !== "ro"; const workspaceRoot = resolveWorkspaceRoot(options?.workspaceDir); + const includeCoreTools = options?.includeCoreTools !== false; const workspaceOnly = fsPolicy.workspaceOnly; const applyPatchConfig = execConfig.applyPatch; // Secure by default: apply_patch is workspace-contained unless explicitly disabled. @@ -486,93 +490,99 @@ export function createOpenClawCodingTools(options?: { } const imageSanitization = resolveImageSanitizationLimits(options?.config); - const base = (createCodingTools(workspaceRoot) as unknown as AnyAgentTool[]).flatMap((tool) => { - if (tool.name === "read") { - if (sandboxRoot) { - const sandboxed = createSandboxedReadTool({ - root: sandboxRoot, - bridge: sandboxFsBridge!, - modelContextWindowTokens: options?.modelContextWindowTokens, - imageSanitization, - }); - return [ - workspaceOnly - ? wrapToolWorkspaceRootGuardWithOptions(sandboxed, sandboxRoot, { - containerWorkdir: sandbox.containerWorkdir, - }) - : sandboxed, - ]; - } - const freshReadTool = createReadTool(workspaceRoot); - const wrapped = createOpenClawReadTool(freshReadTool, { - modelContextWindowTokens: options?.modelContextWindowTokens, - imageSanitization, - }); - return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; - } - if (tool.name === "bash" || tool.name === execToolName) { - return []; - } - if (tool.name === "write") { - if (sandboxRoot) { - return []; - } - const wrapped = createHostWorkspaceWriteTool(workspaceRoot, { workspaceOnly }); - return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; - } - if (tool.name === "edit") { - if (sandboxRoot) { - return []; - } - const wrapped = createHostWorkspaceEditTool(workspaceRoot, { workspaceOnly }); - return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; - } - return [tool]; - }); - const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {}; - const execTool = createLazyExecTool({ - ...execDefaults, - host: options?.exec?.host ?? execConfig.host, - security: options?.exec?.security ?? execConfig.security, - ask: options?.exec?.ask ?? execConfig.ask, - trigger: options?.trigger, - node: options?.exec?.node ?? execConfig.node, - pathPrepend: options?.exec?.pathPrepend ?? execConfig.pathPrepend, - safeBins: options?.exec?.safeBins ?? execConfig.safeBins, - strictInlineEval: options?.exec?.strictInlineEval ?? execConfig.strictInlineEval, - safeBinTrustedDirs: options?.exec?.safeBinTrustedDirs ?? execConfig.safeBinTrustedDirs, - safeBinProfiles: options?.exec?.safeBinProfiles ?? execConfig.safeBinProfiles, - agentId, - cwd: workspaceRoot, - allowBackground, - scopeKey, - sessionKey: options?.sessionKey, - messageProvider: options?.messageProvider, - currentChannelId: options?.currentChannelId, - currentThreadTs: options?.currentThreadTs, - accountId: options?.agentAccountId, - backgroundMs: options?.exec?.backgroundMs ?? execConfig.backgroundMs, - timeoutSec: options?.exec?.timeoutSec ?? execConfig.timeoutSec, - approvalRunningNoticeMs: - options?.exec?.approvalRunningNoticeMs ?? execConfig.approvalRunningNoticeMs, - notifyOnExit: options?.exec?.notifyOnExit ?? execConfig.notifyOnExit, - notifyOnExitEmptySuccess: - options?.exec?.notifyOnExitEmptySuccess ?? execConfig.notifyOnExitEmptySuccess, - sandbox: sandbox - ? { - containerName: sandbox.containerName, - workspaceDir: sandbox.workspaceDir, - containerWorkdir: sandbox.containerWorkdir, - env: sandbox.backend?.env ?? sandbox.docker.env, - buildExecSpec: sandbox.backend?.buildExecSpec.bind(sandbox.backend), - finalizeExec: sandbox.backend?.finalizeExec?.bind(sandbox.backend), + const base = includeCoreTools + ? (createCodingTools(workspaceRoot) as unknown as AnyAgentTool[]).flatMap((tool) => { + if (tool.name === "read") { + if (sandboxRoot) { + const sandboxed = createSandboxedReadTool({ + root: sandboxRoot, + bridge: sandboxFsBridge!, + modelContextWindowTokens: options?.modelContextWindowTokens, + imageSanitization, + }); + return [ + workspaceOnly + ? wrapToolWorkspaceRootGuardWithOptions(sandboxed, sandboxRoot, { + containerWorkdir: sandbox.containerWorkdir, + }) + : sandboxed, + ]; + } + const freshReadTool = createReadTool(workspaceRoot); + const wrapped = createOpenClawReadTool(freshReadTool, { + modelContextWindowTokens: options?.modelContextWindowTokens, + imageSanitization, + }); + return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; } - : undefined, - }); - const processTool = createLazyProcessTool({ - cleanupMs: cleanupMsOverride ?? execConfig.cleanupMs, - scopeKey, - }); + if (tool.name === "bash" || tool.name === execToolName) { + return []; + } + if (tool.name === "write") { + if (sandboxRoot) { + return []; + } + const wrapped = createHostWorkspaceWriteTool(workspaceRoot, { workspaceOnly }); + return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; + } + if (tool.name === "edit") { + if (sandboxRoot) { + return []; + } + const wrapped = createHostWorkspaceEditTool(workspaceRoot, { workspaceOnly }); + return [workspaceOnly ? wrapToolWorkspaceRootGuard(wrapped, workspaceRoot) : wrapped]; + } + return [tool]; + }) + : []; + const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {}; + const execTool = includeCoreTools + ? createLazyExecTool({ + ...execDefaults, + host: options?.exec?.host ?? execConfig.host, + security: options?.exec?.security ?? execConfig.security, + ask: options?.exec?.ask ?? execConfig.ask, + trigger: options?.trigger, + node: options?.exec?.node ?? execConfig.node, + pathPrepend: options?.exec?.pathPrepend ?? execConfig.pathPrepend, + safeBins: options?.exec?.safeBins ?? execConfig.safeBins, + strictInlineEval: options?.exec?.strictInlineEval ?? execConfig.strictInlineEval, + safeBinTrustedDirs: options?.exec?.safeBinTrustedDirs ?? execConfig.safeBinTrustedDirs, + safeBinProfiles: options?.exec?.safeBinProfiles ?? execConfig.safeBinProfiles, + agentId, + cwd: workspaceRoot, + allowBackground, + scopeKey, + sessionKey: options?.sessionKey, + messageProvider: options?.messageProvider, + currentChannelId: options?.currentChannelId, + currentThreadTs: options?.currentThreadTs, + accountId: options?.agentAccountId, + backgroundMs: options?.exec?.backgroundMs ?? execConfig.backgroundMs, + timeoutSec: options?.exec?.timeoutSec ?? execConfig.timeoutSec, + approvalRunningNoticeMs: + options?.exec?.approvalRunningNoticeMs ?? execConfig.approvalRunningNoticeMs, + notifyOnExit: options?.exec?.notifyOnExit ?? execConfig.notifyOnExit, + notifyOnExitEmptySuccess: + options?.exec?.notifyOnExitEmptySuccess ?? execConfig.notifyOnExitEmptySuccess, + sandbox: sandbox + ? { + containerName: sandbox.containerName, + workspaceDir: sandbox.workspaceDir, + containerWorkdir: sandbox.containerWorkdir, + env: sandbox.backend?.env ?? sandbox.docker.env, + buildExecSpec: sandbox.backend?.buildExecSpec.bind(sandbox.backend), + finalizeExec: sandbox.backend?.finalizeExec?.bind(sandbox.backend), + } + : undefined, + }) + : null; + const processTool = includeCoreTools + ? createLazyProcessTool({ + cleanupMs: cleanupMsOverride ?? execConfig.cleanupMs, + scopeKey, + }) + : null; const applyPatchTool = !applyPatchEnabled || (sandboxRoot && !allowWorkspaceWrites) ? null @@ -584,9 +594,53 @@ export function createOpenClawCodingTools(options?: { : undefined, workspaceOnly: applyPatchWorkspaceOnly, }); + const pluginToolAllowlist = collectExplicitAllowlist([ + profilePolicy, + providerProfilePolicy, + globalPolicy, + globalProviderPolicy, + agentPolicy, + agentProviderPolicy, + groupPolicy, + sandboxToolPolicy, + subagentPolicy, + options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : undefined, + ]); + const pluginToolsOnly = includeCoreTools + ? [] + : resolveOpenClawPluginToolsForOptions({ + options: { + agentSessionKey: options?.sessionKey, + agentChannel: resolveGatewayMessageChannel(options?.messageProvider), + agentAccountId: options?.agentAccountId, + agentTo: options?.messageTo, + agentThreadId: options?.messageThreadId, + agentDir: options?.agentDir, + workspaceDir: workspaceRoot, + config: options?.config, + fsPolicy, + requesterSenderId: options?.senderId, + senderIsOwner: options?.senderIsOwner, + sessionId: options?.sessionId, + sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl, + allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true, + sandboxed: !!sandbox, + pluginToolAllowlist, + currentChannelId: options?.currentChannelId, + currentThreadTs: options?.currentThreadTs, + currentMessageId: options?.currentMessageId, + modelProvider: options?.modelProvider, + modelHasVision: options?.modelHasVision, + requireExplicitMessageTarget: options?.requireExplicitMessageTarget, + disableMessageTool: options?.disableMessageTool, + requesterAgentIdOverride: agentId, + allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding, + }, + resolvedConfig: options?.config, + }); const tools: AnyAgentTool[] = [ ...base, - ...(sandboxRoot + ...(includeCoreTools && sandboxRoot ? allowWorkspaceWrites ? [ workspaceOnly @@ -610,65 +664,56 @@ export function createOpenClawCodingTools(options?: { ] : [] : []), - ...(applyPatchTool ? [applyPatchTool as unknown as AnyAgentTool] : []), - execTool as unknown as AnyAgentTool, - processTool as unknown as AnyAgentTool, + ...(includeCoreTools && applyPatchTool ? [applyPatchTool as unknown as AnyAgentTool] : []), + ...(execTool ? [execTool as unknown as AnyAgentTool] : []), + ...(processTool ? [processTool as unknown as AnyAgentTool] : []), // Channel docking: include channel-defined agent tools (login, etc.). - ...listChannelAgentTools({ cfg: options?.config }), - ...createOpenClawTools({ - sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl, - allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true, - agentSessionKey: options?.sessionKey, - agentChannel: resolveGatewayMessageChannel(options?.messageProvider), - agentAccountId: options?.agentAccountId, - agentTo: options?.messageTo, - agentThreadId: options?.messageThreadId, - agentGroupId: options?.groupId ?? null, - agentGroupChannel: options?.groupChannel ?? null, - agentGroupSpace: options?.groupSpace ?? null, - agentMemberRoleIds: options?.memberRoleIds, - agentDir: options?.agentDir, - sandboxRoot, - sandboxContainerWorkdir: sandbox?.containerWorkdir, - sandboxFsBridge, - fsPolicy, - workspaceDir: workspaceRoot, - spawnWorkspaceDir: options?.spawnWorkspaceDir - ? resolveWorkspaceRoot(options.spawnWorkspaceDir) - : undefined, - sandboxed: !!sandbox, - config: options?.config, - pluginToolAllowlist: collectExplicitAllowlist([ - profilePolicy, - providerProfilePolicy, - globalPolicy, - globalProviderPolicy, - agentPolicy, - agentProviderPolicy, - groupPolicy, - sandboxToolPolicy, - subagentPolicy, - options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : undefined, - ]), - currentChannelId: options?.currentChannelId, - currentThreadTs: options?.currentThreadTs, - currentMessageId: options?.currentMessageId, - modelProvider: options?.modelProvider, - modelId: options?.modelId, - replyToMode: options?.replyToMode, - hasRepliedRef: options?.hasRepliedRef, - modelHasVision: options?.modelHasVision, - requireExplicitMessageTarget: options?.requireExplicitMessageTarget, - disableMessageTool: options?.disableMessageTool, - enableHeartbeatTool, - ...(cronSelfRemoveOnlyJobId ? { cronSelfRemoveOnlyJobId } : {}), - requesterAgentIdOverride: agentId, - requesterSenderId: options?.senderId, - senderIsOwner: options?.senderIsOwner, - sessionId: options?.sessionId, - onYield: options?.onYield, - allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding, - }), + ...(includeCoreTools ? listChannelAgentTools({ cfg: options?.config }) : []), + ...(includeCoreTools + ? createOpenClawTools({ + sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl, + allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true, + agentSessionKey: options?.sessionKey, + agentChannel: resolveGatewayMessageChannel(options?.messageProvider), + agentAccountId: options?.agentAccountId, + agentTo: options?.messageTo, + agentThreadId: options?.messageThreadId, + agentGroupId: options?.groupId ?? null, + agentGroupChannel: options?.groupChannel ?? null, + agentGroupSpace: options?.groupSpace ?? null, + agentMemberRoleIds: options?.memberRoleIds, + agentDir: options?.agentDir, + sandboxRoot, + sandboxContainerWorkdir: sandbox?.containerWorkdir, + sandboxFsBridge, + fsPolicy, + workspaceDir: workspaceRoot, + spawnWorkspaceDir: options?.spawnWorkspaceDir + ? resolveWorkspaceRoot(options.spawnWorkspaceDir) + : undefined, + sandboxed: !!sandbox, + config: options?.config, + pluginToolAllowlist, + currentChannelId: options?.currentChannelId, + currentThreadTs: options?.currentThreadTs, + currentMessageId: options?.currentMessageId, + modelProvider: options?.modelProvider, + modelId: options?.modelId, + replyToMode: options?.replyToMode, + hasRepliedRef: options?.hasRepliedRef, + modelHasVision: options?.modelHasVision, + requireExplicitMessageTarget: options?.requireExplicitMessageTarget, + disableMessageTool: options?.disableMessageTool, + enableHeartbeatTool, + ...(cronSelfRemoveOnlyJobId ? { cronSelfRemoveOnlyJobId } : {}), + requesterAgentIdOverride: agentId, + requesterSenderId: options?.senderId, + senderIsOwner: options?.senderIsOwner, + sessionId: options?.sessionId, + onYield: options?.onYield, + allowGatewaySubagentBinding: options?.allowGatewaySubagentBinding, + }) + : pluginToolsOnly), ]; const toolsForMemoryFlush = isMemoryFlushRun && memoryFlushWritePath