fix(subagent-session-spawning): resolved subagent parent id propagation

- Forwarded `parentAgentId` through task and eval launch paths when spawning subagents.
- Mapped `parentAgentId` to `parentId` in `createAgentSession`.
- Passed each caller's session `getAgentId` (or `MAIN_AGENT_ID`) as the parent for spawned agents.
This commit is contained in:
can1357
2026-06-16 23:03:11 +02:00
parent 3225e6b4be
commit 1fa4f2eee8
11 changed files with 89 additions and 7 deletions
@@ -33,13 +33,16 @@ const taskAgent: AgentDefinition = {
source: "bundled",
};
function createSession(options: { manager?: AsyncJobManager; settings?: Record<string, unknown> } = {}): ToolSession {
function createSession(
options: { manager?: AsyncJobManager; settings?: Record<string, unknown>; agentId?: string } = {},
): ToolSession {
return {
cwd: "/tmp",
hasUI: false,
settings: Settings.isolated(options.settings ?? {}),
getSessionFile: () => null,
getSessionSpawns: () => "*",
getAgentId: () => options.agentId ?? null,
asyncJobManager: options.manager,
} as unknown as ToolSession;
}
@@ -243,15 +246,20 @@ describe("task.batch spawning", () => {
it("spawns one background job per task item and forwards the shared context", async () => {
mockDiscovery();
const seen: Array<{ id?: string; context?: string; assignment?: string }> = [];
const seen: Array<{ id?: string; context?: string; assignment?: string; parentAgentId?: string }> = [];
vi.spyOn(executorModule, "runSubprocess").mockImplementation(async options => {
seen.push({ id: options.id, context: options.context, assignment: options.assignment });
seen.push({
id: options.id,
context: options.context,
assignment: options.assignment,
parentAgentId: options.parentAgentId,
});
return makeResult(options.id ?? "?");
});
const manager = createManager();
const tool = await TaskTool.create(
createSession({ manager, settings: { "async.enabled": true, "task.batch": true } }),
createSession({ manager, agentId: "ParentA", settings: { "async.enabled": true, "task.batch": true } }),
);
const result = await tool.execute("tc-batch", {
@@ -287,6 +295,9 @@ describe("task.batch spawning", () => {
expect(spawn.context).toBe("# Goal\nShared background.");
}
expect(seen.map(spawn => spawn.assignment).sort()).toEqual(["Do A.", "Do B."]);
// Every spawn is parented to the spawning agent (not to itself): the
// registry "of <parent>" link must be the caller, never the child's id.
for (const spawn of seen) expect(spawn.parentAgentId).toBe("ParentA");
});
it("treats a one-item batch as a single spawn and forwards context", async () => {