diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts index af100b503c..dda6f27e4b 100644 --- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts +++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts @@ -58,7 +58,6 @@ import { agentGraphIdForRootSession } from '@maka/runtime/stream-graph-coordinat import { resolveTurnShellPlan, ShellPreferenceError } from '@maka/runtime/shell-detect'; import { buildParentAgentTools } from '@maka/runtime/subagent-tools'; import { SESSION_RECAP_INSTRUCTION } from '@maka/runtime/session-recap'; -import { hostedExecutionToolNames } from '../server/hosted-execution-tool-profile.js'; import { createToolResultArchiveCapability } from '@maka/runtime/tool-result-archive-capability'; import { loadHistoryCompactCheckpointsFromRunLedger } from '@maka/runtime/history-compact-ledger'; import { stableHash, toolCatalogHash } from '@maka/runtime/request-shape'; @@ -2967,7 +2966,6 @@ test('the headless coding profile freezes the Eval prompt and tool ceiling', asy } as unknown as HostMemoryCoordinator, taskLedger: {} as TaskLedgerStore, builtinTools: {}, - boundToolNames: hostedExecutionToolNames('headless-coding-v1'), toolProfile: 'headless-coding-v1', parentAgentTools: buildParentAgentTools(), scheduledTaskTool: { diff --git a/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts b/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts index a71c6b93bd..3e3bf291bc 100644 --- a/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts +++ b/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts @@ -81,7 +81,33 @@ test('the headless coding profile freezes prompt, tools, memory, and foreground }), impl: async () => 'ok', }; - const [bash] = projectHostedExecutionTools([original], 'headless-coding-v1'); + const profileTools = projectHostedExecutionTools( + [ + original, + ...profile.toolNames + .filter((name) => name !== 'Bash') + .map( + (name): MakaTool => ({ + name, + description: name, + parameters: z.object({}), + impl: async () => 'ok', + }), + ), + { + name: 'ScheduledTask', + description: 'Must stay outside the profile ceiling', + parameters: z.object({}), + impl: async () => 'scheduled', + }, + ], + 'headless-coding-v1', + ); + assert.deepEqual( + profileTools.map(({ name }) => name), + profile.toolNames, + ); + const bash = profileTools[0]; assert.ok(bash); const schema = bash.parameters as z.ZodType; assert.equal((await schema.safeParseAsync({ command: 'true' })).success, true); diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts index 0a5d199a9e..153b25ccb3 100644 --- a/packages/runtime-host/src/server/execution-composition.ts +++ b/packages/runtime-host/src/server/execution-composition.ts @@ -742,12 +742,7 @@ export async function createExecutionRuntimeHostComposition( skills, memory: requireMemory(memory), taskLedger, - ...(runProfile - ? { - boundToolNames: runProfile.toolNames, - toolProfile: header.toolProfile, - } - : {}), + ...(runProfile ? { toolProfile: header.toolProfile } : {}), ...(capabilitySnapshot ? { clientCapabilities: capabilitySnapshot } : {}), builtinTools, hostTools: surface.hostTools, diff --git a/packages/runtime-host/src/server/hosted-execution-tool-profile.ts b/packages/runtime-host/src/server/hosted-execution-tool-profile.ts index 151aebb05a..35e28d0aec 100644 --- a/packages/runtime-host/src/server/hosted-execution-tool-profile.ts +++ b/packages/runtime-host/src/server/hosted-execution-tool-profile.ts @@ -69,17 +69,19 @@ export function hostedExecutionRunProfile( throw new Error('Unknown Session tool profile'); } -export function hostedExecutionToolNames(profile: SessionToolProfile): readonly string[] { - return hostedExecutionRunProfile(profile)!.toolNames; -} - export function projectHostedExecutionTools( tools: readonly MakaTool[], profile: SessionToolProfile | undefined, ): readonly MakaTool[] { if (profile === undefined) return tools; - hostedExecutionRunProfile(profile); - return tools.map((tool) => + const toolNames = hostedExecutionRunProfile(profile)!.toolNames; + const byName = new Map(tools.map((tool) => [tool.name, tool])); + const selected = toolNames.map((name) => byName.get(name)); + const missing = toolNames.filter((_name, index) => selected[index] === undefined); + if (missing.length > 0) { + throw new Error(`Hosted tool profile is unavailable: ${missing.join(', ')}`); + } + return (selected as MakaTool[]).map((tool) => tool.name === 'Bash' ? { ...tool, diff --git a/packages/runtime-host/src/server/interactive-run-composer.ts b/packages/runtime-host/src/server/interactive-run-composer.ts index f7cfdda08f..b350d50f36 100644 --- a/packages/runtime-host/src/server/interactive-run-composer.ts +++ b/packages/runtime-host/src/server/interactive-run-composer.ts @@ -117,7 +117,6 @@ export interface InteractiveRunComposerInput { readonly childInstruction?: string; readonly sideConversation?: boolean; readonly boundTools?: readonly MakaTool[]; - readonly boundToolNames?: readonly string[]; readonly toolProfile?: SessionToolProfile; readonly skillBudget?: SkillCatalogBudgetOptions; readonly platform?: NodeJS.Platform; @@ -155,9 +154,6 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput) const inventorySnapshotFor = createTurnSkillInventorySnapshotResolver(input.skills); const inventoryFor: SkillInventoryResolver = async (context) => (await inventorySnapshotFor(context)).inventory; - if (input.boundTools && input.boundToolNames) { - throw new Error('Interactive tool bindings are ambiguous'); - } const defaultTools = input.boundTools ? input.boundTools : buildDefaultHostTools( @@ -171,16 +167,13 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput) input.plan, input.deepResearch?.tools, ); - const clientCapabilityTools = - input.boundTools || input.boundToolNames ? [] : (input.clientCapabilities?.tools ?? []); + const hasToolCeiling = input.boundTools !== undefined || input.toolProfile !== undefined; + const clientCapabilityTools = hasToolCeiling ? [] : (input.clientCapabilities?.tools ?? []); const unscopedCandidateTools = [...defaultTools, ...clientCapabilityTools]; const routedCandidateTools = input.deepResearch ? unscopedCandidateTools.filter(isDeepResearchToolAllowed) : unscopedCandidateTools; - const boundCandidateTools = input.boundToolNames - ? bindToolsByName(routedCandidateTools, input.boundToolNames) - : routedCandidateTools; - const candidateTools = projectHostedExecutionTools(boundCandidateTools, input.toolProfile); + const candidateTools = projectHostedExecutionTools(routedCandidateTools, input.toolProfile); const activeExecution = input.plan ? activePlanExecution(input.plan.state) : undefined; const selectedTools = input.plan ? selectCollaborationTools({ @@ -194,8 +187,7 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput) host: 'runtime-host', tools: selectedTools, policy: { - economy: - input.boundTools || input.boundToolNames ? false : !process.env.MAKA_DISABLE_DEFERRED_TOOLS, + economy: hasToolCeiling ? false : !process.env.MAKA_DISABLE_DEFERRED_TOOLS, }, }); // A bound tool list is an exact child/local activation ceiling. Dynamic @@ -204,7 +196,7 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput) assertUniqueToolNames(tools); const toolAvailability = mergeToolAvailability( productSurface.toolAvailability, - input.boundTools + hasToolCeiling ? [] : filterToolGroups( input.clientCapabilities?.groups ?? [], @@ -329,26 +321,10 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput) }); } -function bindToolsByName( - tools: readonly MakaTool[], - names: readonly string[], -): readonly MakaTool[] { - if (new Set(names).size !== names.length) { - throw new Error('Hosted tool profile contains duplicate tool names'); - } - const byName = new Map(tools.map((tool) => [tool.name, tool])); - const selected = names.map((name) => byName.get(name)); - const missing = names.filter((_name, index) => selected[index] === undefined); - if (missing.length > 0) { - throw new Error(`Hosted tool profile is unavailable: ${missing.join(', ')}`); - } - return selected as MakaTool[]; -} - export interface InteractiveRunComposerFactoryInput extends Omit< InteractiveRunComposerInput, - 'runtimePolicy' | 'boundTools' | 'boundToolNames' | 'clientCapabilities' | 'plan' + 'runtimePolicy' | 'boundTools' | 'clientCapabilities' | 'plan' > { readonly clientCapabilities: HostClientCapabilityCoordinator; readonly resolveTavilyWebSearchReadiness: () => Promise; @@ -475,11 +451,7 @@ export function createInteractiveRunComposerFactory( : {}), ...(boundTools ? { boundTools } : {}), ...(!boundTools && backendContext.header.toolProfile - ? { - boundToolNames: hostedExecutionRunProfile(backendContext.header.toolProfile)! - .toolNames, - toolProfile: backendContext.header.toolProfile, - } + ? { toolProfile: backendContext.header.toolProfile } : {}), ...(clientCapabilities ? { clientCapabilities } : {}), ...(input.builtinTools ? { builtinTools: input.builtinTools } : {}),