test: narrow subagent spawn test seams

This commit is contained in:
Peter Steinberger
2026-04-03 13:53:01 +01:00
parent 88bc6d852f
commit 35d890b5ef
3 changed files with 93 additions and 124 deletions

View File

@@ -0,0 +1,23 @@
export { formatThinkingLevels, normalizeThinkLevel } from "../auto-reply/thinking.js";
export { DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH } from "../config/agent-limits.js";
export { loadConfig } from "../config/config.js";
export { mergeSessionEntry, updateSessionStore } from "../config/sessions.js";
export { callGateway } from "../gateway/call.js";
export { ADMIN_SCOPE, isAdminOnlyMethod } from "../gateway/method-scopes.js";
export {
pruneLegacyStoreKeys,
resolveGatewaySessionStoreTarget,
} from "../gateway/session-utils.js";
export { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
export { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
export { normalizeDeliveryContext } from "../utils/delivery-context.js";
export { resolveAgentConfig } from "./agent-scope.js";
export { AGENT_LANE_SUBAGENT } from "./lanes.js";
export { resolveSubagentSpawnModelSelection } from "./model-selection.js";
export { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js";
export { buildSubagentSystemPrompt } from "./subagent-announce.js";
export {
resolveDisplaySessionKey,
resolveInternalSessionKey,
resolveMainSessionAlias,
} from "./tools/sessions-helpers.js";

View File

@@ -122,117 +122,62 @@ export async function loadSubagentSpawnModuleForTest(params: {
}) {
vi.resetModules();
vi.doMock("../gateway/call.js", () => ({
vi.doMock("./subagent-spawn.runtime.js", () => ({
callGateway: (opts: unknown) => params.callGatewayMock(opts),
buildSubagentSystemPrompt: () => "system-prompt",
getGlobalHookRunner: () => params.hookRunner ?? { hasHooks: () => false },
emitSessionLifecycleEvent: (...args: unknown[]) =>
params.emitSessionLifecycleEventMock?.(...args),
formatThinkingLevels: (levels: string[]) => levels.join(", "),
normalizeThinkLevel: (level: unknown) =>
typeof level === "string" && level.trim() ? level.trim() : undefined,
DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH: 3,
ADMIN_SCOPE: "operator.admin",
AGENT_LANE_SUBAGENT: "subagent",
loadConfig: () =>
params.loadConfig?.() ?? createSubagentSpawnTestConfig(params.workspaceDir ?? os.tmpdir()),
mergeSessionEntry: (
current: Record<string, unknown> | undefined,
next: Record<string, unknown>,
) => ({
...current,
...next,
}),
updateSessionStore:
params.updateSessionStoreMock ??
(async (_storePath: string, mutator: SessionStoreMutator) => {
const store: SessionStore = {};
await mutator(store);
return store;
}),
isAdminOnlyMethod: (method: string) =>
method === "sessions.patch" || method === "sessions.delete",
pruneLegacyStoreKeys: (...args: unknown[]) => params.pruneLegacyStoreKeysMock?.(...args),
resolveGatewaySessionStoreTarget: (targetParams: { key: string }) => ({
agentId: "main",
storePath: params.sessionStorePath ?? "/tmp/subagent-spawn-model-session.json",
canonicalKey: targetParams.key,
storeKeys: [targetParams.key],
}),
normalizeDeliveryContext: identityDeliveryContext,
resolveAgentConfig: params.resolveAgentConfig ?? (() => undefined),
resolveAgentWorkspaceDir:
params.resolveAgentWorkspaceDir ?? (() => params.workspaceDir ?? os.tmpdir()),
resolveSubagentSpawnModelSelection:
params.resolveSubagentSpawnModelSelection ??
((spawnParams: { modelOverride?: unknown }) =>
typeof spawnParams.modelOverride === "string" && spawnParams.modelOverride.trim()
? spawnParams.modelOverride.trim()
: "openai/gpt-4"),
resolveSandboxRuntimeStatus:
params.resolveSandboxRuntimeStatus ?? (() => ({ sandboxed: false })),
...createDefaultSessionHelperMocks(),
}));
vi.doMock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () =>
params.loadConfig?.() ?? createSubagentSpawnTestConfig(params.workspaceDir ?? os.tmpdir()),
};
});
if (params.updateSessionStoreMock) {
vi.doMock("../config/sessions.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/sessions.js")>();
return {
...actual,
updateSessionStore: (...args: unknown[]) => params.updateSessionStoreMock?.(...args),
};
});
}
if (params.pruneLegacyStoreKeysMock) {
vi.doMock("../gateway/session-utils.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../gateway/session-utils.js")>();
return {
...actual,
resolveGatewaySessionStoreTarget: (targetParams: { key: string }) => ({
agentId: "main",
storePath: params.sessionStorePath ?? "/tmp/subagent-spawn-model-session.json",
canonicalKey: targetParams.key,
storeKeys: [targetParams.key],
}),
pruneLegacyStoreKeys: (...args: unknown[]) => params.pruneLegacyStoreKeysMock?.(...args),
};
});
}
if (params.emitSessionLifecycleEventMock) {
vi.doMock("../sessions/session-lifecycle-events.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../sessions/session-lifecycle-events.js")>();
return {
...actual,
emitSessionLifecycleEvent: (...args: unknown[]) =>
params.emitSessionLifecycleEventMock?.(...args),
};
});
}
vi.doMock("./subagent-announce.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./subagent-announce.js")>();
return {
...actual,
buildSubagentSystemPrompt: () => "system-prompt",
};
});
vi.doMock("./agent-scope.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./agent-scope.js")>();
return {
...actual,
resolveAgentConfig: params.resolveAgentConfig ?? actual.resolveAgentConfig,
resolveAgentWorkspaceDir:
params.resolveAgentWorkspaceDir ?? (() => params.workspaceDir ?? os.tmpdir()),
};
});
vi.doMock("./subagent-depth.js", () => ({
getSubagentDepthFromSessionStore: () => 0,
}));
vi.doMock("./model-selection.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./model-selection.js")>();
return {
...actual,
resolveSubagentSpawnModelSelection:
params.resolveSubagentSpawnModelSelection ?? actual.resolveSubagentSpawnModelSelection,
};
});
vi.doMock("./sandbox/runtime-status.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./sandbox/runtime-status.js")>();
return {
...actual,
resolveSandboxRuntimeStatus:
params.resolveSandboxRuntimeStatus ?? actual.resolveSandboxRuntimeStatus,
};
});
vi.doMock("../plugins/hook-runner-global.js", () => ({
getGlobalHookRunner: () => params.hookRunner ?? { hasHooks: () => false },
}));
vi.doMock("../utils/delivery-context.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../utils/delivery-context.js")>();
return {
...actual,
normalizeDeliveryContext: identityDeliveryContext,
};
});
vi.doMock("./tools/sessions-helpers.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./tools/sessions-helpers.js")>();
return {
...actual,
...createDefaultSessionHelperMocks(),
};
});
const subagentRegistry = await import("./subagent-registry.js");
if (params.registerSubagentRunMock) {
vi.spyOn(subagentRegistry, "registerSubagentRun").mockImplementation(

View File

@@ -1,16 +1,5 @@
import crypto from "node:crypto";
import { promises as fs } from "node:fs";
import { formatThinkingLevels, normalizeThinkLevel } from "../auto-reply/thinking.js";
import { DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH } from "../config/agent-limits.js";
import { loadConfig } from "../config/config.js";
import { mergeSessionEntry, updateSessionStore } from "../config/sessions.js";
import { callGateway } from "../gateway/call.js";
import { ADMIN_SCOPE, isAdminOnlyMethod } from "../gateway/method-scopes.js";
import {
pruneLegacyStoreKeys,
resolveGatewaySessionStoreTarget,
} from "../gateway/session-utils.js";
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js";
import {
isValidAgentId,
@@ -18,18 +7,11 @@ import {
normalizeAgentId,
parseAgentSessionKey,
} from "../routing/session-key.js";
import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
import { resolveAgentConfig } from "./agent-scope.js";
import { AGENT_LANE_SUBAGENT } from "./lanes.js";
import { resolveSubagentSpawnModelSelection } from "./model-selection.js";
import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js";
import {
mapToolContextToSpawnedRunMetadata,
normalizeSpawnedRunMetadata,
resolveSpawnedWorkspaceInheritance,
} from "./spawned-context.js";
import { buildSubagentSystemPrompt } from "./subagent-announce.js";
import {
decodeStrictBase64,
materializeSubagentAttachments,
@@ -38,12 +20,31 @@ import {
import { resolveSubagentCapabilities } from "./subagent-capabilities.js";
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
import { countActiveRunsForSession, registerSubagentRun } from "./subagent-registry.js";
import { readStringParam } from "./tools/common.js";
import {
ADMIN_SCOPE,
AGENT_LANE_SUBAGENT,
DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH,
buildSubagentSystemPrompt,
callGateway,
emitSessionLifecycleEvent,
formatThinkingLevels,
getGlobalHookRunner,
loadConfig,
mergeSessionEntry,
normalizeDeliveryContext,
normalizeThinkLevel,
pruneLegacyStoreKeys,
resolveAgentConfig,
resolveDisplaySessionKey,
resolveGatewaySessionStoreTarget,
resolveInternalSessionKey,
resolveMainSessionAlias,
} from "./tools/sessions-helpers.js";
resolveSandboxRuntimeStatus,
resolveSubagentSpawnModelSelection,
updateSessionStore,
isAdminOnlyMethod,
} from "./subagent-spawn.runtime.js";
import { readStringParam } from "./tools/common.js";
export const SUBAGENT_SPAWN_MODES = ["run", "session"] as const;
export type SpawnSubagentMode = (typeof SUBAGENT_SPAWN_MODES)[number];