Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 1 addition & 6 deletions packages/runtime-host/src/server/execution-composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
42 changes: 7 additions & 35 deletions packages/runtime-host/src/server/interactive-run-composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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({
Expand All @@ -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
Expand All @@ -204,7 +196,7 @@ export function createInteractiveRunComposer(input: InteractiveRunComposerInput)
assertUniqueToolNames(tools);
const toolAvailability = mergeToolAvailability(
productSurface.toolAvailability,
input.boundTools
hasToolCeiling
? []
: filterToolGroups(
input.clientCapabilities?.groups ?? [],
Expand Down Expand Up @@ -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<boolean>;
Expand Down Expand Up @@ -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 } : {}),
Expand Down