Skip to content

Commit 9c49bf8

Browse files
committed
fix(runtime): Preserve daemon next-step params
Skip manifest template re-application for daemon-routed responses. Daemon-invoked tools are already post-processed before returning to CLI. A second template merge in CLI could overwrite concrete dynamic params such as logSessionId, producing incomplete follow-up command hints.
1 parent 87c389c commit 9c49bf8

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

src/runtime/__tests__/tool-invoker.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,67 @@ describe('DefaultToolInvoker next steps post-processing', () => {
375375
]);
376376
});
377377

378+
it('preserves daemon-provided next-step params when nextStepParams are already consumed', async () => {
379+
daemonClientMock.invokeTool.mockResolvedValue({
380+
content: [{ type: 'text', text: 'ok' }],
381+
nextSteps: [
382+
{
383+
tool: 'stop_sim_log_cap',
384+
label: 'Stop capture and retrieve logs',
385+
params: { logSessionId: 'session-123' },
386+
priority: 1,
387+
},
388+
],
389+
} satisfies ToolResponse);
390+
391+
const catalog = createToolCatalog([
392+
makeTool({
393+
id: 'start_sim_log_cap',
394+
cliName: 'start-simulator-log-capture',
395+
mcpName: 'start_sim_log_cap',
396+
workflow: 'logging',
397+
stateful: true,
398+
nextStepTemplates: [
399+
{
400+
label: 'Stop capture and retrieve logs',
401+
toolId: 'stop_sim_log_cap',
402+
priority: 1,
403+
},
404+
],
405+
handler: vi.fn().mockResolvedValue(textResponse('start')),
406+
}),
407+
makeTool({
408+
id: 'stop_sim_log_cap',
409+
cliName: 'stop-simulator-log-capture',
410+
mcpName: 'stop_sim_log_cap',
411+
workflow: 'logging',
412+
stateful: true,
413+
handler: vi.fn().mockResolvedValue(textResponse('stop')),
414+
}),
415+
]);
416+
417+
const invoker = new DefaultToolInvoker(catalog);
418+
const response = await invoker.invoke(
419+
'start-simulator-log-capture',
420+
{},
421+
{
422+
runtime: 'cli',
423+
socketPath: '/tmp/xcodebuildmcp.sock',
424+
},
425+
);
426+
427+
expect(response.nextSteps).toEqual([
428+
{
429+
tool: 'stop_sim_log_cap',
430+
label: 'Stop capture and retrieve logs',
431+
params: { logSessionId: 'session-123' },
432+
priority: 1,
433+
workflow: 'logging',
434+
cliTool: 'stop-simulator-log-capture',
435+
},
436+
]);
437+
});
438+
378439
it('overrides unresolved template placeholders with dynamic next-step params', async () => {
379440
const directHandler = vi.fn().mockResolvedValue({
380441
content: [{ type: 'text', text: 'ok' }],

src/runtime/tool-invoker.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,14 @@ export function postProcessToolResponse(params: {
147147
response: ToolResponse;
148148
catalog: ToolCatalog;
149149
runtime: InvokeOptions['runtime'];
150+
applyTemplateNextSteps?: boolean;
150151
}): ToolResponse {
151-
const { tool, response, catalog, runtime } = params;
152+
const { tool, response, catalog, runtime, applyTemplateNextSteps = true } = params;
152153

153154
const templateSteps = buildTemplateNextSteps(tool, catalog);
154155

155156
const withTemplates =
156-
templateSteps.length > 0
157+
applyTemplateNextSteps && templateSteps.length > 0
157158
? {
158159
...response,
159160
nextSteps: mergeTemplateAndResponseNextSteps(templateSteps, response.nextStepParams),
@@ -303,6 +304,7 @@ export class DefaultToolInvoker implements ToolInvoker {
303304
return postProcessToolResponse({
304305
...context.postProcessParams,
305306
response,
307+
applyTemplateNextSteps: false,
306308
});
307309
} catch (error) {
308310
log(

0 commit comments

Comments
 (0)