fix(cli): validate agentId in fn_task_create/update and unblock bundle tests
fn_task_create and fn_task_update accepted any string as `agentId` and wrote it verbatim onto `task.assignedAgentId`, letting hallucinated IDs (e.g. `agent-executor-001`) appear as agent badges in the dashboard. Mirror the validation already used by fn_delegate: look the agent up via AgentStore and reject unknown or ephemeral/runtime-managed agents. Null still clears the field on update. Also clean up two stale failures in bundle-output.test that predated this change: - pi-claude-cli no longer imports cross-spawn, so drop the dependency and its orphan type-decl file. - Loosen the spawn-import regex to match `spawn` anywhere in the destructured import (the source has additional named imports). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,30 @@ function getFusionDir(cwd: string): string {
|
||||
return join(resolveProjectRoot(cwd), ".fusion");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an agent id supplied to task create/update tools.
|
||||
* Returns null on success, or an error message describing why the id was rejected.
|
||||
*
|
||||
* Rejects unknown agents and ephemeral/runtime-managed agents — mirrors fn_delegate
|
||||
* so callers can't park hallucinated or task-worker IDs in `task.assignedAgentId`.
|
||||
*/
|
||||
async function validateAssignableAgentId(
|
||||
cwd: string,
|
||||
agentId: string,
|
||||
): Promise<string | null> {
|
||||
const { AgentStore, isEphemeralAgent } = await import("@fusion/core");
|
||||
const agentStore = new AgentStore({ rootDir: getFusionDir(cwd) });
|
||||
await agentStore.init();
|
||||
const agent = await agentStore.getAgent(agentId);
|
||||
if (!agent) {
|
||||
return `Agent ${agentId} not found`;
|
||||
}
|
||||
if (isEphemeralAgent(agent)) {
|
||||
return `Cannot assign task to ephemeral/runtime agent ${agentId}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatTaskLine(t: Task): string {
|
||||
const label =
|
||||
t.title || t.description.slice(0, 60) + (t.description.length > 60 ? "…" : "");
|
||||
@@ -167,6 +191,18 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const store = await getStore(ctx.cwd);
|
||||
|
||||
if (params.agentId !== undefined) {
|
||||
const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
|
||||
if (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: error }],
|
||||
isError: true,
|
||||
details: { error },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const task = await store.createTask({
|
||||
description: params.description.trim(),
|
||||
dependencies: params.depends,
|
||||
@@ -273,6 +309,16 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
updatedFields.push("dependencies");
|
||||
}
|
||||
if (params.agentId !== undefined) {
|
||||
if (params.agentId !== null) {
|
||||
const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
|
||||
if (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: error }],
|
||||
isError: true,
|
||||
details: { error },
|
||||
};
|
||||
}
|
||||
}
|
||||
updates.assignedAgentId = params.agentId;
|
||||
updatedFields.push("agentId");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user