diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index e5011f5f0a61..c203e382a92e 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -319,8 +319,8 @@ packages/tool-call-repair/src/stream-normalizer.test.ts packages/tool-call-repair/src/stream-normalizer.ts src/acp/control-plane/manager.test.ts src/acp/control-plane/manager.turn-results.test.ts -src/agents/acp-spawn-parent-stream.test.ts -src/agents/acp-spawn.test.ts +src/agents/subagents/spawn/acp-spawn-parent-stream.test.ts +src/agents/subagents/spawn/acp-spawn.test.ts src/agents/agent-bundle-mcp-runtime.test.ts src/agents/agent-bundle-mcp-runtime.ts src/agents/agent-command.live-model-switch.test.ts @@ -454,7 +454,7 @@ src/agents/subagent-registry-lifecycle.test.ts src/agents/subagent-registry-run-manager.ts src/agents/subagent-registry.steer-restart.test.ts src/agents/subagent-registry.test.ts -src/agents/subagent-spawn.test.ts +src/agents/subagents/spawn/subagent-spawn.test.ts src/agents/system-prompt.test.ts src/agents/system-prompt.ts src/agents/tool-display-common.ts diff --git a/docs/plan/swarms.md b/docs/plan/swarms.md index 6a50d4392367..804def16b55f 100644 --- a/docs/plan/swarms.md +++ b/docs/plan/swarms.md @@ -100,7 +100,7 @@ New `tools.swarm` (global + per-agent override, same merge pattern as carries `structured: undefined` plus the raw text and a `schemaError`. - `fastMode: true | "auto" | false` — threaded into the child session patch alongside model/thinking via `resolveSubagentModelAndThinkingPlan` - (`src/agents/subagent-spawn-plan.ts`), using the existing `FastMode` axis + (`src/agents/subagents/spawn/subagent-spawn-plan.ts`), using the existing `FastMode` axis (`src/shared/fast-mode.ts`). Omitted = inherit. - `groupId: string` — swarm group stamp. Defaults to `swarm::`. Persisted on the diff --git a/qa/scenarios/agents/subagent-fanout-synthesis.yaml b/qa/scenarios/agents/subagent-fanout-synthesis.yaml index 033fa19b98e0..b6edd50183e7 100644 --- a/qa/scenarios/agents/subagent-fanout-synthesis.yaml +++ b/qa/scenarios/agents/subagent-fanout-synthesis.yaml @@ -18,7 +18,7 @@ scenario: - docs/tools/subagents.md - docs/help/testing.md codeRefs: - - src/agents/subagent-spawn.ts + - src/agents/subagents/spawn/subagent-spawn.ts - src/agents/system-prompt.ts - extensions/qa-lab/src/suite.ts execution: diff --git a/qa/scenarios/agents/subagent-forked-context.yaml b/qa/scenarios/agents/subagent-forked-context.yaml index 264bcaf55071..1da122e2f964 100644 --- a/qa/scenarios/agents/subagent-forked-context.yaml +++ b/qa/scenarios/agents/subagent-forked-context.yaml @@ -17,7 +17,7 @@ scenario: - docs/concepts/session-tool.md codeRefs: - src/agents/tools/sessions-spawn-tool.ts - - src/agents/subagent-spawn.ts + - src/agents/subagents/spawn/subagent-spawn.ts execution: kind: flow summary: Ask the agent to delegate work that depends on the current transcript and assert sessions_spawn carries context=fork. diff --git a/scripts/audit-seams.mts b/scripts/audit-seams.mts index 912d43f27032..2f9b0776a6d2 100644 --- a/scripts/audit-seams.mts +++ b/scripts/audit-seams.mts @@ -689,12 +689,16 @@ function describeSubagentSeamKinds(relativePath: string, source: string) { relativePath === "src/agents/subagent-announce-dispatch.ts"; const importsSpawnRuntime = hasAnyImportSource(source, [ "./subagent-spawn.js", - "../subagent-spawn.js", "./acp-spawn.js", - "../acp-spawn.js", + "./subagents/spawn/subagent-spawn.js", + "../subagents/spawn/subagent-spawn.js", + "./subagents/spawn/acp-spawn.js", + "../subagents/spawn/acp-spawn.js", "./subagent-registry.js", "../subagent-registry.js", + "../../subagent-registry.js", "../acp/control-plane/manager.js", + "../../../acp/control-plane/manager.js", ]); const importsLifecycleRegistry = hasAnyImportSource(source, [ "./subagent-registry-completion.js", @@ -705,6 +709,9 @@ function describeSubagentSeamKinds(relativePath: string, source: string) { "../context-engine/init.js", "../context-engine/registry.js", "../sessions/session-lifecycle-events.js", + "../../../context-engine/init.js", + "../../../context-engine/registry.js", + "../../../sessions/session-lifecycle-events.js", ]); const importsAnnounceDelivery = hasAnyImportSource(source, [ "./subagent-announce.js", @@ -713,17 +720,25 @@ function describeSubagentSeamKinds(relativePath: string, source: string) { "../infra/outbound/bound-delivery-router.js", "../utils/delivery-context.shared.js", "../gateway/call.js", + "../../../infra/outbound/bound-delivery-router.js", + "../../../utils/delivery-context.shared.js", + "../../../gateway/call.js", ]); const importsCleanup = hasAnyImportSource(source, [ "../gateway/call.js", "./subagent-registry-cleanup.js", "../acp/control-plane/spawn.js", + "../../../gateway/call.js", + "../../../acp/control-plane/spawn.js", ]); const importsParentStream = hasAnyImportSource(source, [ "./acp-spawn-parent-stream.js", "../infra/heartbeat-wake.js", "../infra/system-events.js", "../infra/agent-events.js", + "../../../infra/heartbeat-wake.js", + "../../../infra/system-events.js", + "../../../infra/agent-events.js", ]); if ( diff --git a/scripts/check-channel-agnostic-boundaries.mts b/scripts/check-channel-agnostic-boundaries.mts index 924fd9aa0c9a..e37ce99bf32e 100644 --- a/scripts/check-channel-agnostic-boundaries.mts +++ b/scripts/check-channel-agnostic-boundaries.mts @@ -17,7 +17,7 @@ const repoRoot = resolveRepoRoot(import.meta.url); const acpCoreProtectedSources = [ path.join(repoRoot, "src", "acp"), - path.join(repoRoot, "src", "agents", "acp-spawn.ts"), + path.join(repoRoot, "src", "agents", "subagents", "spawn", "acp-spawn.ts"), path.join(repoRoot, "src", "auto-reply", "reply", "commands-acp"), path.join(repoRoot, "src", "infra", "outbound", "conversation-id.ts"), ]; diff --git a/scripts/check-session-accessor-boundary.mts b/scripts/check-session-accessor-boundary.mts index f1fac02f6f6b..764da11df9bb 100644 --- a/scripts/check-session-accessor-boundary.mts +++ b/scripts/check-session-accessor-boundary.mts @@ -101,7 +101,7 @@ export const migratedSessionAccessorFiles = new Set([ "src/acp/control-plane/manager.background-task.ts", "src/acp/control-plane/manager.core.ts", "src/acp/runtime/session-meta.ts", - "src/agents/acp-spawn.ts", + "src/agents/subagents/spawn/acp-spawn.ts", "src/agents/auth-profiles/session-override.ts", "src/agents/embedded-agent-runner/compaction-successor-transcript.ts", "src/agents/embedded-agent-runner/run/attempt.ts", diff --git a/scripts/lib/session-accessor-debt-baseline.json b/scripts/lib/session-accessor-debt-baseline.json index e2d7b5d8cce5..3b7c632e1c9f 100644 --- a/scripts/lib/session-accessor-debt-baseline.json +++ b/scripts/lib/session-accessor-debt-baseline.json @@ -9,7 +9,7 @@ "src/config/sessions/session-accessor.entry.ts": 2 }, "sessionAccessorWrite": { - "src/agents/subagent-spawn.test-helpers.ts": 1, + "src/agents/subagents/spawn/subagent-spawn.test-helpers.ts": 1, "src/plugins/registry-runtime.ts": 1 }, "sessionCompactManualTrim": {}, diff --git a/src/agents/acp-binding-architecture.guardrail.test.ts b/src/agents/acp-binding-architecture.guardrail.test.ts index c85c96920a6b..65d912250d45 100644 --- a/src/agents/acp-binding-architecture.guardrail.test.ts +++ b/src/agents/acp-binding-architecture.guardrail.test.ts @@ -13,7 +13,7 @@ type GuardedSource = { const GUARDED_SOURCES: GuardedSource[] = [ { - path: "agents/acp-spawn.ts", + path: "agents/subagents/spawn/acp-spawn.ts", forbiddenPatterns: [/\bgetThreadBindingManager\b/, /\bparseDiscordTarget\b/], }, { diff --git a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts index c6c4f2b90484..4e671a132a6f 100644 --- a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts @@ -410,7 +410,7 @@ vi.mock("../../sessions/sdk.js", () => ({ hoisted.createAgentSessionMock(...args), })); -vi.mock("../../subagent-spawn.js", () => ({ +vi.mock("../../subagents/spawn/subagent-spawn.js", () => ({ SUBAGENT_SPAWN_MODES: ["run", "session"], spawnSubagentDirect: (...args: unknown[]) => hoisted.spawnSubagentDirectMock(...args), })); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.allowlist.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.allowlist.test.ts index 4472a97c2910..8711465f76dc 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.allowlist.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.allowlist.test.ts @@ -4,7 +4,7 @@ import { createSubagentSpawnTestConfig, loadSubagentSpawnModuleForTest, setupAcceptedSubagentGatewayMock, -} from "./subagent-spawn.test-helpers.js"; +} from "./subagents/spawn/subagent-spawn.test-helpers.js"; const hoisted = vi.hoisted(() => ({ callGatewayMock: vi.fn(), @@ -12,7 +12,7 @@ const hoisted = vi.hoisted(() => ({ })); let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; -let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; +let spawnSubagentDirect: typeof import("./subagents/spawn/subagent-spawn.js").spawnSubagentDirect; function resolveAgentConfigFromList(cfg: Record, agentId: string) { const agents = (cfg.agents as { list?: Array> } | undefined)?.list; diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.plan.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.plan.test.ts index 910bf7854ef9..e499b265cd10 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.plan.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.plan.test.ts @@ -3,13 +3,13 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js"; import { resolveConfiguredSubagentSpawnModelSelection } from "./model-selection.js"; -import { resolveSubagentSpawnAcceptedNote } from "./subagent-spawn-accepted-note.js"; +import { resolveSubagentSpawnAcceptedNote } from "./subagents/spawn/subagent-spawn-accepted-note.js"; import { resolveConfiguredSubagentRunTimeoutSeconds, resolveSubagentModelAndThinkingPlan, splitModelRef, -} from "./subagent-spawn-plan.js"; -import { resolveSubagentThinkingOverride } from "./subagent-spawn-thinking.js"; +} from "./subagents/spawn/subagent-spawn-plan.js"; +import { resolveSubagentThinkingOverride } from "./subagents/spawn/subagent-spawn-thinking.js"; type SubagentModelPlan = ReturnType; type OkSubagentModelPlan = Extract; diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts index cd35c2d98d6b..3271c39539f0 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts @@ -14,7 +14,8 @@ type RunSubagentAnnounceFlow = (typeof import("./subagent-announce.js"))["runSub type CreateSessionsSpawnTool = (typeof import("./tools/sessions-spawn-tool.js"))["createSessionsSpawnTool"]; type SubagentRegistryTesting = (typeof import("./subagent-registry.test-helpers.js"))["testing"]; -type SubagentSpawnTesting = (typeof import("./subagent-spawn.test-support.js"))["testing"]; +type SubagentSpawnTesting = + (typeof import("./subagents/spawn/subagent-spawn.test-support.js"))["testing"]; type CreateOpenClawToolsOpts = Parameters[0]; type GatewayRequest = { method?: string; params?: unknown; timeoutMs?: number }; type AgentWaitCall = { runId?: string; timeoutMs?: number }; @@ -194,7 +195,7 @@ export async function getSessionsSpawnTool(opts: CreateOpenClawToolsOpts) { if (!cachedSubagentSpawnTesting || !cachedSubagentRegistryTesting) { const [{ testing: subagentSpawnTesting }, { testing: subagentRegistryTesting }] = await Promise.all([ - import("./subagent-spawn.test-support.js"), + import("./subagents/spawn/subagent-spawn.test-support.js"), import("./subagent-registry.test-helpers.js"), ]); cachedSubagentSpawnTesting = subagentSpawnTesting; diff --git a/src/agents/sessions-spawn-hooks.test.ts b/src/agents/sessions-spawn-hooks.test.ts index 4a2debf73582..e746f4fc8370 100644 --- a/src/agents/sessions-spawn-hooks.test.ts +++ b/src/agents/sessions-spawn-hooks.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { createSubagentSpawnTestConfig, loadSubagentSpawnModuleForTest, -} from "./subagent-spawn.test-helpers.js"; +} from "./subagents/spawn/subagent-spawn.test-helpers.js"; type GatewayRequest = { method?: string; params?: Record }; type TestBindingRequest = { @@ -57,7 +57,7 @@ const bindingMocks = vi.hoisted(() => ({ })); let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; -let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; +let spawnSubagentDirect: typeof import("./subagents/spawn/subagent-spawn.js").spawnSubagentDirect; function getGatewayRequests(): GatewayRequest[] { // Gateway call list is the observable side effect for spawn orchestration. diff --git a/src/agents/subagent-announce-descendant-wake.ts b/src/agents/subagent-announce-descendant-wake.ts index 65bdded8e8f2..62e30eee759e 100644 --- a/src/agents/subagent-announce-descendant-wake.ts +++ b/src/agents/subagent-announce-descendant-wake.ts @@ -14,7 +14,7 @@ import type { dispatchGatewayMethodInProcess, getRuntimeConfig, } from "./subagent-announce.runtime.js"; -import { terminateAcceptedCollectorRun } from "./subagent-spawn-cleanup.js"; +import { terminateAcceptedCollectorRun } from "./subagents/spawn/subagent-spawn-cleanup.js"; type DescendantWakeDeps = { callGateway: typeof callGateway; diff --git a/src/agents/subagent-announce-origin.ts b/src/agents/subagent-announce-origin.ts index 0795ca5b73c2..b363b48d329b 100644 --- a/src/agents/subagent-announce-origin.ts +++ b/src/agents/subagent-announce-origin.ts @@ -39,7 +39,7 @@ import { getGlobalHookRunner, resolveConversationIdFromTargets, } from "./subagent-announce-delivery.runtime.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; +import type { SpawnSubagentMode } from "./subagents/spawn/subagent-spawn.types.js"; export type { DeliveryContext } from "../utils/delivery-context.types.js"; function normalizeAnnounceRouteTarget(context?: DeliveryContext): string | undefined { diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index 94aad6678a34..66c3c51e856d 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -59,7 +59,7 @@ import { } from "./subagent-announce.runtime.js"; import { getSubagentDepthFromSessionStore } from "./subagent-depth.js"; import { deleteSubagentSessionForCleanup } from "./subagent-session-cleanup.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; +import type { SpawnSubagentMode } from "./subagents/spawn/subagent-spawn.types.js"; import { isAnnounceSkip } from "./tools/sessions-send-tokens.js"; type SubagentAnnounceDeps = { diff --git a/src/agents/subagent-control.ts b/src/agents/subagent-control.ts index bcff33d21646..dfc7b7c12307 100644 --- a/src/agents/subagent-control.ts +++ b/src/agents/subagent-control.ts @@ -54,7 +54,7 @@ import { replaceSubagentRunAfterSteer, } from "./subagent-registry.js"; import type { SubagentRunRecord } from "./subagent-registry.types.js"; -import { terminateAcceptedCollectorRun } from "./subagent-spawn-cleanup.js"; +import { terminateAcceptedCollectorRun } from "./subagents/spawn/subagent-spawn-cleanup.js"; import { resolveInternalSessionKey, resolveMainSessionAlias } from "./tools/sessions-helpers.js"; /** Recent-run default window used by subagent control UI/tools. */ diff --git a/src/agents/subagent-registry-restore.ts b/src/agents/subagent-registry-restore.ts index 0ed3b69d4c72..c73cd3786bcb 100644 --- a/src/agents/subagent-registry-restore.ts +++ b/src/agents/subagent-registry-restore.ts @@ -21,8 +21,8 @@ import { loadSubagentSessionEntry, type SubagentSessionStoreCache, } from "./subagent-session-reconciliation.js"; -import { retrySubagentCleanup } from "./subagent-spawn-cleanup.js"; -import { readGatewayRunId } from "./subagent-spawn-gateway.js"; +import { retrySubagentCleanup } from "./subagents/spawn/subagent-spawn-cleanup.js"; +import { readGatewayRunId } from "./subagents/spawn/subagent-spawn-gateway.js"; import { resolveSwarmConfig } from "./subagents/swarm/swarm-config.js"; import { enqueueSwarmRun } from "./subagents/swarm/swarm-scheduler.js"; diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 9d93b7076b2c..67f9268b8cc5 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -47,7 +47,7 @@ import { resolveSubagentSessionCompletion, resolveSubagentSessionStartedAt, } from "./subagent-session-reconciliation.js"; -import { terminateAcceptedCollectorRun } from "./subagent-spawn-cleanup.js"; +import { terminateAcceptedCollectorRun } from "./subagents/spawn/subagent-spawn-cleanup.js"; export type { SubagentRunRecord } from "./subagent-registry.types.js"; const log = createSubsystemLogger("agents/subagent-registry"); diff --git a/src/agents/subagent-registry.types.ts b/src/agents/subagent-registry.types.ts index e205ef5f0033..203aa3ed316a 100644 --- a/src/agents/subagent-registry.types.ts +++ b/src/agents/subagent-registry.types.ts @@ -6,7 +6,7 @@ import type { AgentRunSessionTarget } from "./run-session-target.js"; import type { SubagentRunOutcome } from "./subagent-announce-output.js"; import type { SubagentLaunchAuthorization } from "./subagent-launch-authorization.js"; import type { SubagentLifecycleEndedReason } from "./subagent-lifecycle-events.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; +import type { SpawnSubagentMode } from "./subagents/spawn/subagent-spawn.types.js"; export type SubagentCompletionRequest = { runId: string; diff --git a/src/agents/subagent-session-cleanup.ts b/src/agents/subagent-session-cleanup.ts index 6ff5ea48000f..9160df6854ad 100644 --- a/src/agents/subagent-session-cleanup.ts +++ b/src/agents/subagent-session-cleanup.ts @@ -4,7 +4,7 @@ */ import { SESSION_LIFECYCLE_CHANGED_ERROR_REASON } from "../config/sessions/lifecycle.js"; import type { callGateway } from "../gateway/call.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; +import type { SpawnSubagentMode } from "./subagents/spawn/subagent-spawn.types.js"; type CallGateway = (options: Parameters[0]) => Promise; type SubagentSessionCleanupOutcome = "deleted" | "changed" | "failed"; diff --git a/src/agents/subagent-spawn.runtime.ts b/src/agents/subagent-spawn.runtime.ts deleted file mode 100644 index a229d53feb4e..000000000000 --- a/src/agents/subagent-spawn.runtime.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Runtime dependency barrel for subagent spawning. Keeping these imports in a - * single module lets spawn tests replace runtime seams without loading the - * entire gateway/channel stack. - */ -export { getRuntimeConfig } from "../config/config.js"; -export { - loadSessionEntryReadOnly as loadSessionEntry, - upsertSessionEntry, -} from "../config/sessions/session-accessor.js"; -export { forkSessionEntryFromParent } from "../auto-reply/reply/session-fork.js"; -export { ensureContextEnginesInitialized } from "../context-engine/init.js"; -export { resolveContextEngine } from "../context-engine/registry.js"; -export { callGateway } from "../gateway/call.js"; -export { - dispatchGatewayMethodInProcess, - hasInProcessGatewayContext, -} from "../gateway/server-plugins.js"; -export { - ADMIN_SCOPE, - resolveLeastPrivilegeOperatorScopesForMethod, -} from "../gateway/method-scopes.js"; -export { getSessionBindingService } from "../infra/outbound/session-binding-service.js"; -export { resolveGatewaySessionStoreTarget } from "../gateway/session-utils.js"; -export { getGlobalHookRunner } from "../plugins/hook-runner-global.js"; -export { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; -export { - mergeDeliveryContext, - normalizeDeliveryContext, -} from "../utils/delivery-context.shared.js"; -export { resolveAgentConfig } from "./agent-scope.js"; -export { AGENT_LANE_SUBAGENT } from "./lanes.js"; -export { loadPreparedModelCatalog } from "./prepared-model-catalog.js"; -export { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js"; -export { buildSubagentSystemPrompt } from "./subagent-system-prompt.js"; -export { resolveInternalSessionKey, resolveMainSessionAlias } from "./tools/sessions-helpers.js"; diff --git a/src/agents/acp-spawn-admission.ts b/src/agents/subagents/spawn/acp-spawn-admission.ts similarity index 91% rename from src/agents/acp-spawn-admission.ts rename to src/agents/subagents/spawn/acp-spawn-admission.ts index 5ad8e8a8e823..e880701e2b7b 100644 --- a/src/agents/acp-spawn-admission.ts +++ b/src/agents/subagents/spawn/acp-spawn-admission.ts @@ -1,6 +1,6 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { listTasksForOwnerKey } from "../tasks/runtime-internal.js"; -import { getSubagentRunByChildSessionKey } from "./subagent-registry.js"; +import { listTasksForOwnerKey } from "../../../tasks/runtime-internal.js"; +import { getSubagentRunByChildSessionKey } from "../../subagent-registry.js"; function isActiveTaskStatus(status: string | undefined): boolean { return status === "queued" || status === "running"; diff --git a/src/agents/acp-spawn-bootstrap-delivery.ts b/src/agents/subagents/spawn/acp-spawn-bootstrap-delivery.ts similarity index 92% rename from src/agents/acp-spawn-bootstrap-delivery.ts rename to src/agents/subagents/spawn/acp-spawn-bootstrap-delivery.ts index d6036e4e2be0..2b5ab823a7d9 100644 --- a/src/agents/acp-spawn-bootstrap-delivery.ts +++ b/src/agents/subagents/spawn/acp-spawn-bootstrap-delivery.ts @@ -1,17 +1,17 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { AcpTurnAttachment } from "../acp/control-plane/manager.types.js"; +import type { AcpTurnAttachment } from "../../../acp/control-plane/manager.types.js"; import { formatConversationTarget, routeFromBindingRecord, routeToDeliveryFields, -} from "../channels/route-projection.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { SessionBindingRecord } from "../infra/outbound/session-binding-service.js"; -import type { AcpSpawnRequesterState } from "./acp-spawn-requester.js"; +} from "../../../channels/route-projection.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { SessionBindingRecord } from "../../../infra/outbound/session-binding-service.js"; import { resolveConversationRefForThreadBinding, resolveSpawnChannelAccountId, -} from "./spawn-plan.js"; +} from "../../spawn-plan.js"; +import type { AcpSpawnRequesterState } from "./acp-spawn-requester.js"; type GatewayImageAttachmentInput = { type: "image"; diff --git a/src/agents/acp-spawn-heartbeat.ts b/src/agents/subagents/spawn/acp-spawn-heartbeat.ts similarity index 78% rename from src/agents/acp-spawn-heartbeat.ts rename to src/agents/subagents/spawn/acp-spawn-heartbeat.ts index 2640877d0130..48a2a1ba9159 100644 --- a/src/agents/acp-spawn-heartbeat.ts +++ b/src/agents/subagents/spawn/acp-spawn-heartbeat.ts @@ -1,14 +1,14 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { DEFAULT_HEARTBEAT_EVERY } from "../auto-reply/heartbeat.js"; -import { parseDurationMs } from "../cli/parse-duration.js"; -import { resolveStorePath } from "../config/sessions/paths.js"; -import { loadSessionEntryReadOnly } from "../config/sessions/session-accessor.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { areHeartbeatsEnabled } from "../infra/heartbeat-wake.js"; -import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js"; -import { deliveryContextFromSession } from "../utils/delivery-context.shared.js"; -import { listAgentEntries } from "./agent-scope-config.js"; -import { resolveAgentConfig, resolveDefaultAgentId } from "./agent-scope.js"; +import { DEFAULT_HEARTBEAT_EVERY } from "../../../auto-reply/heartbeat.js"; +import { parseDurationMs } from "../../../cli/parse-duration.js"; +import { resolveStorePath } from "../../../config/sessions/paths.js"; +import { loadSessionEntryReadOnly } from "../../../config/sessions/session-accessor.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { areHeartbeatsEnabled } from "../../../infra/heartbeat-wake.js"; +import { normalizeAgentId, parseAgentSessionKey } from "../../../routing/session-key.js"; +import { deliveryContextFromSession } from "../../../utils/delivery-context.shared.js"; +import { listAgentEntries } from "../../agent-scope-config.js"; +import { resolveAgentConfig, resolveDefaultAgentId } from "../../agent-scope.js"; export function isHeartbeatEnabledForSessionAgent(params: { cfg: OpenClawConfig; diff --git a/src/agents/acp-spawn-parent-stream.test.ts b/src/agents/subagents/spawn/acp-spawn-parent-stream.test.ts similarity index 98% rename from src/agents/acp-spawn-parent-stream.test.ts rename to src/agents/subagents/spawn/acp-spawn-parent-stream.test.ts index d3a7f28f6a7e..8ad47b955933 100644 --- a/src/agents/acp-spawn-parent-stream.test.ts +++ b/src/agents/subagents/spawn/acp-spawn-parent-stream.test.ts @@ -1,20 +1,20 @@ /** Tests ACP child-to-parent stream relay notices and routing. */ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { mergeMockedModule } from "../test-utils/vitest-module-mocks.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { mergeMockedModule } from "../../../test-utils/vitest-module-mocks.js"; const enqueueSystemEventMock = vi.fn(); const requestHeartbeatMock = vi.fn(); const recordAcpParentStreamEventsMock = vi.fn(); -vi.mock("../infra/system-events.js", () => ({ +vi.mock("../../../infra/system-events.js", () => ({ enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args), })); -vi.mock("../infra/heartbeat-wake.js", async () => { +vi.mock("../../../infra/heartbeat-wake.js", async () => { return await mergeMockedModule( - await vi.importActual( - "../infra/heartbeat-wake.js", + await vi.importActual( + "../../../infra/heartbeat-wake.js", ), () => ({ requestHeartbeat: (...args: unknown[]) => requestHeartbeatMock(...args), @@ -22,10 +22,10 @@ vi.mock("../infra/heartbeat-wake.js", async () => { ); }); -vi.mock("./acp-parent-stream-store.sqlite.js", async () => { +vi.mock("../../acp-parent-stream-store.sqlite.js", async () => { return await mergeMockedModule( - await vi.importActual( - "./acp-parent-stream-store.sqlite.js", + await vi.importActual( + "../../acp-parent-stream-store.sqlite.js", ), () => ({ recordAcpParentStreamEvents: (...args: unknown[]) => recordAcpParentStreamEventsMock(...args), @@ -33,7 +33,7 @@ vi.mock("./acp-parent-stream-store.sqlite.js", async () => { ); }); -let emitAgentEvent: typeof import("../infra/agent-events.js").emitAgentEvent; +let emitAgentEvent: typeof import("../../../infra/agent-events.js").emitAgentEvent; let startAcpSpawnParentStreamRelay: typeof import("./acp-spawn-parent-stream.js").startAcpSpawnParentStreamRelay; const progressCommentaryDeliveryContext = { @@ -86,7 +86,7 @@ function firstMockCall( describe("startAcpSpawnParentStreamRelay", () => { beforeAll(async () => { - ({ emitAgentEvent } = await import("../infra/agent-events.js")); + ({ emitAgentEvent } = await import("../../../infra/agent-events.js")); ({ startAcpSpawnParentStreamRelay } = await import("./acp-spawn-parent-stream.js")); }); diff --git a/src/agents/acp-spawn-parent-stream.ts b/src/agents/subagents/spawn/acp-spawn-parent-stream.ts similarity index 96% rename from src/agents/acp-spawn-parent-stream.ts rename to src/agents/subagents/spawn/acp-spawn-parent-stream.ts index 075e5b60c040..b9c9e634800b 100644 --- a/src/agents/acp-spawn-parent-stream.ts +++ b/src/agents/subagents/spawn/acp-spawn-parent-stream.ts @@ -11,30 +11,30 @@ import { isAcpTagVisible, resolveAcpProjectionSettings, type AcpProjectionSettings, -} from "../auto-reply/reply/acp-stream-settings.js"; +} from "../../../auto-reply/reply/acp-stream-settings.js"; import { resolveChannelStreamingProgressCommentary, type StreamingCompatEntry, -} from "../channels/streaming.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { onAgentEvent } from "../infra/agent-events.js"; +} from "../../../channels/streaming.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { onAgentEvent } from "../../../infra/agent-events.js"; import { type EventSessionRoutingPolicy, resolveEventSessionKeyForPolicy, scopedHeartbeatWakeOptionsForPolicy, -} from "../infra/event-session-routing.js"; -import { requestHeartbeat } from "../infra/heartbeat-wake.js"; -import { enqueueSystemEvent } from "../infra/system-events.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; -import { resolveNormalizedAccountEntry } from "../routing/account-lookup.js"; -import { normalizeAccountId } from "../routing/session-key.js"; -import { normalizeAssistantPhase } from "../shared/chat-message-content.js"; -import { recordTaskRunProgressByRunId } from "../tasks/detached-task-runtime.js"; -import type { DeliveryContext } from "../utils/delivery-context.types.js"; +} from "../../../infra/event-session-routing.js"; +import { requestHeartbeat } from "../../../infra/heartbeat-wake.js"; +import { enqueueSystemEvent } from "../../../infra/system-events.js"; +import { createSubsystemLogger } from "../../../logging/subsystem.js"; +import { resolveNormalizedAccountEntry } from "../../../routing/account-lookup.js"; +import { normalizeAccountId } from "../../../routing/session-key.js"; +import { normalizeAssistantPhase } from "../../../shared/chat-message-content.js"; +import { recordTaskRunProgressByRunId } from "../../../tasks/detached-task-runtime.js"; +import type { DeliveryContext } from "../../../utils/delivery-context.types.js"; import { recordAcpParentStreamEvents, type AcpParentStreamEvent, -} from "./acp-parent-stream-store.sqlite.js"; +} from "../../acp-parent-stream-store.sqlite.js"; const DEFAULT_STREAM_FLUSH_MS = 2_500; const DEFAULT_NO_OUTPUT_NOTICE_MS = 60_000; diff --git a/src/agents/acp-spawn-requester.ts b/src/agents/subagents/spawn/acp-spawn-requester.ts similarity index 88% rename from src/agents/acp-spawn-requester.ts rename to src/agents/subagents/spawn/acp-spawn-requester.ts index 0f0ef1b6aeec..311953d881da 100644 --- a/src/agents/acp-spawn-requester.ts +++ b/src/agents/subagents/spawn/acp-spawn-requester.ts @@ -1,25 +1,28 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { readAcpSessionMeta } from "../acp/runtime/session-meta.js"; -import { resolveStorePath } from "../config/sessions/paths.js"; +import { readAcpSessionMeta } from "../../../acp/runtime/session-meta.js"; +import { resolveStorePath } from "../../../config/sessions/paths.js"; import { listSessionEntriesReadOnly, loadSessionEntryReadOnly, resolveSessionTranscriptRuntimeTarget, -} from "../config/sessions/session-accessor.js"; -import type { SessionAcpMeta, SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { formatErrorMessage } from "../infra/errors.js"; -import { getSessionBindingService } from "../infra/outbound/session-binding-service.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; -import { isSubagentSessionKey, parseAgentSessionKey } from "../routing/session-key.js"; -import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +} from "../../../config/sessions/session-accessor.js"; +import type { SessionAcpMeta, SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { formatErrorMessage } from "../../../infra/errors.js"; +import { getSessionBindingService } from "../../../infra/outbound/session-binding-service.js"; +import { createSubsystemLogger } from "../../../logging/subsystem.js"; +import { isSubagentSessionKey, parseAgentSessionKey } from "../../../routing/session-key.js"; +import { normalizeDeliveryContext } from "../../../utils/delivery-context.shared.js"; +import { resolveRequesterOriginForChild } from "../../spawn-requester-origin.js"; +import type { SessionCapabilityStore } from "../../subagent-capabilities.js"; +import { + resolveInternalSessionKey, + resolveMainSessionAlias, +} from "../../tools/sessions-helpers.js"; import { hasSessionLocalHeartbeatRelayRoute, isHeartbeatEnabledForSessionAgent, } from "./acp-spawn-heartbeat.js"; -import { resolveRequesterOriginForChild } from "./spawn-requester-origin.js"; -import type { SessionCapabilityStore } from "./subagent-capabilities.js"; -import { resolveInternalSessionKey, resolveMainSessionAlias } from "./tools/sessions-helpers.js"; const log = createSubsystemLogger("agents/acp-spawn"); diff --git a/src/agents/acp-spawn-runtime.ts b/src/agents/subagents/spawn/acp-spawn-runtime.ts similarity index 90% rename from src/agents/acp-spawn-runtime.ts rename to src/agents/subagents/spawn/acp-spawn-runtime.ts index c4a7dea60c99..eeca4b615b53 100644 --- a/src/agents/acp-spawn-runtime.ts +++ b/src/agents/subagents/spawn/acp-spawn-runtime.ts @@ -4,33 +4,33 @@ import { } from "@openclaw/acp-core/runtime/session-identifiers"; import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { getAcpSessionManager } from "../acp/control-plane/manager.js"; -import type { AcpSpawnRuntimeCloseHandle } from "../acp/control-plane/spawn.js"; -import { formatThinkingLevels } from "../auto-reply/thinking.js"; +import { getAcpSessionManager } from "../../../acp/control-plane/manager.js"; +import type { AcpSpawnRuntimeCloseHandle } from "../../../acp/control-plane/spawn.js"; +import { formatThinkingLevels } from "../../../auto-reply/thinking.js"; import { resolveThreadBindingIntroText, resolveThreadBindingThreadName, -} from "../channels/thread-bindings-messages.js"; +} from "../../../channels/thread-bindings-messages.js"; import { resolveThreadBindingIdleTimeoutMsForChannel, resolveThreadBindingMaxAgeMsForChannel, -} from "../channels/thread-bindings-policy.js"; -import { resolveStorePath } from "../config/sessions/paths.js"; -import { loadSessionEntry } from "../config/sessions/session-accessor.js"; -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { isMissingPathError } from "../infra/errors.js"; +} from "../../../channels/thread-bindings-policy.js"; +import { resolveStorePath } from "../../../config/sessions/paths.js"; +import { loadSessionEntry } from "../../../config/sessions/session-accessor.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { isMissingPathError } from "../../../infra/errors.js"; import { getSessionBindingService, type SessionBindingRecord, -} from "../infra/outbound/session-binding-service.js"; -import { persistAcpSpawnSessionFileBestEffort } from "./acp-spawn-requester.js"; -import { resolveAgentConfig } from "./agent-scope.js"; +} from "../../../infra/outbound/session-binding-service.js"; +import { resolveAgentConfig } from "../../agent-scope.js"; import { resolveConfiguredSubagentSpawnModelSelection, resolveThinkingDefault, -} from "./model-selection.js"; -import type { PreparedSpawnThreadBinding } from "./spawn-plan.js"; +} from "../../model-selection.js"; +import type { PreparedSpawnThreadBinding } from "../../spawn-plan.js"; +import { persistAcpSpawnSessionFileBestEffort } from "./acp-spawn-requester.js"; import { splitModelRef } from "./subagent-spawn-plan.js"; import { resolveSubagentThinkingOverride } from "./subagent-spawn-thinking.js"; diff --git a/src/agents/acp-spawn-target.ts b/src/agents/subagents/spawn/acp-spawn-target.ts similarity index 90% rename from src/agents/acp-spawn-target.ts rename to src/agents/subagents/spawn/acp-spawn-target.ts index a75071c8100f..6106d8b93c81 100644 --- a/src/agents/acp-spawn-target.ts +++ b/src/agents/subagents/spawn/acp-spawn-target.ts @@ -1,7 +1,7 @@ -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { normalizeOptionalAgentId } from "../routing/session-key.js"; -import { listAgentEntries } from "./agent-scope-config.js"; -import { listAgentIds } from "./agent-scope.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { normalizeOptionalAgentId } from "../../../routing/session-key.js"; +import { listAgentEntries } from "../../agent-scope-config.js"; +import { listAgentIds } from "../../agent-scope.js"; export function resolveTargetAcpAgentId(params: { requestedAgentId?: string; diff --git a/src/agents/acp-spawn.test.ts b/src/agents/subagents/spawn/acp-spawn.test.ts similarity index 97% rename from src/agents/acp-spawn.test.ts rename to src/agents/subagents/spawn/acp-spawn.test.ts index 16a8ae7ebc22..7be3c10c16b6 100644 --- a/src/agents/acp-spawn.test.ts +++ b/src/agents/subagents/spawn/acp-spawn.test.ts @@ -4,33 +4,33 @@ import os from "node:os"; import path from "node:path"; import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { AcpInitializeSessionInput } from "../acp/control-plane/manager.types.js"; -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { setGatewayDedupeEntry, waitForAgentJob } from "../gateway/agent-turn/agent-job.js"; -import type { CallGatewayOptions } from "../gateway/call.js"; -import type { DedupeEntry } from "../gateway/server-shared.js"; +import type { AcpInitializeSessionInput } from "../../../acp/control-plane/manager.types.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { setGatewayDedupeEntry, waitForAgentJob } from "../../../gateway/agent-turn/agent-job.js"; +import type { CallGatewayOptions } from "../../../gateway/call.js"; +import type { DedupeEntry } from "../../../gateway/server-shared.js"; import { testing as sessionBindingServiceTesting, registerSessionBindingAdapter, type SessionBindingAdapter, type SessionBindingPlacement, type SessionBindingRecord, -} from "../infra/outbound/session-binding-service.js"; -import { normalizeSessionDeliveryState } from "../utils/delivery-context.shared.js"; -import type { AgentRunTerminalReplySnapshot } from "./agent-run-terminal-reply.js"; -import { reserveChildAdmissionSlot } from "./child-admission.js"; -import { createAcpVisibleTextAccumulator } from "./command/attempt-execution.helpers.js"; +} from "../../../infra/outbound/session-binding-service.js"; +import { normalizeSessionDeliveryState } from "../../../utils/delivery-context.shared.js"; +import type { AgentRunTerminalReplySnapshot } from "../../agent-run-terminal-reply.js"; +import { reserveChildAdmissionSlot } from "../../child-admission.js"; +import { createAcpVisibleTextAccumulator } from "../../command/attempt-execution.helpers.js"; import { buildAcpResult, createAcpToolLifecycleTracker, emitAcpLifecycleEnd, -} from "./command/attempt-execution.js"; -import { resolveThinkingDefault } from "./model-selection.js"; -import { SUBAGENT_ENDED_REASON_COMPLETE } from "./subagent-lifecycle-events.js"; -import { createSubagentRegistryLifecycleController } from "./subagent-registry-lifecycle.js"; -import type { RegisterSubagentRunParams } from "./subagent-registry-run-manager.js"; -import type { SubagentRunRecord } from "./subagent-registry.types.js"; +} from "../../command/attempt-execution.js"; +import { resolveThinkingDefault } from "../../model-selection.js"; +import { SUBAGENT_ENDED_REASON_COMPLETE } from "../../subagent-lifecycle-events.js"; +import { createSubagentRegistryLifecycleController } from "../../subagent-registry-lifecycle.js"; +import type { RegisterSubagentRunParams } from "../../subagent-registry-run-manager.js"; +import type { SubagentRunRecord } from "../../subagent-registry.types.js"; type SessionBindingAdapterCapabilities = NonNullable; type BoundaryLifecycleControllerParams = Parameters< @@ -189,59 +189,59 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("../acp/control-plane/manager.js", () => ({ +vi.mock("../../../acp/control-plane/manager.js", () => ({ getAcpSessionManager: hoisted.getAcpSessionManagerMock, })); -vi.mock("../acp/control-plane/spawn.js", () => ({ +vi.mock("../../../acp/control-plane/spawn.js", () => ({ cleanupFailedAcpSpawn: hoisted.cleanupFailedAcpSpawnMock, })); -vi.mock("../acp/runtime/session-meta.js", () => ({ +vi.mock("../../../acp/runtime/session-meta.js", () => ({ readAcpSessionMeta: (params: unknown) => hoisted.readAcpSessionMetaMock(params), })); -vi.mock("../channels/plugins/index.js", () => ({ +vi.mock("../../../channels/plugins/index.js", () => ({ getChannelPlugin: hoisted.getChannelPluginMock, getLoadedChannelPlugin: hoisted.getLoadedChannelPluginMock, normalizeChannelId: hoisted.normalizeChannelIdMock, })); -vi.mock("../channels/plugins/registry.js", () => ({ +vi.mock("../../../channels/plugins/registry.js", () => ({ getChannelPlugin: hoisted.getChannelPluginMock, getLoadedChannelPlugin: hoisted.getLoadedChannelPluginMock, normalizeChannelId: hoisted.normalizeChannelIdMock, })); -vi.mock("../config/sessions/paths.js", () => ({ +vi.mock("../../../config/sessions/paths.js", () => ({ resolveStorePath: hoisted.resolveStorePathMock, })); -vi.mock("../config/sessions/session-accessor.js", async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock("../../../config/sessions/session-accessor.js", async (importOriginal) => ({ + ...(await importOriginal()), ...hoisted.createSessionAccessorMock(), })); -vi.mock("../config/sessions.js", () => ({ +vi.mock("../../../config/sessions.js", () => ({ loadSessionStore: hoisted.loadSessionStoreMock, resolveAgentIdFromSessionKey: (sessionKey: string) => sessionKey.match(/^agent:([^:]+)/)?.[1] ?? "main", resolveStorePath: hoisted.resolveStorePathMock, })); -vi.mock("../config/config.js", () => ({ +vi.mock("../../../config/config.js", () => ({ getRuntimeConfig: () => hoisted.state.cfg, })); -vi.mock("../config/sessions/transcript.js", () => ({ +vi.mock("../../../config/sessions/transcript.js", () => ({ resolveSessionTranscriptFile: hoisted.resolveSessionTranscriptFileMock, })); -vi.mock("../gateway/call.js", () => ({ +vi.mock("../../../gateway/call.js", () => ({ callGateway: hoisted.callGatewayMock, })); -vi.mock("../infra/heartbeat-wake.js", () => ({ +vi.mock("../../../infra/heartbeat-wake.js", () => ({ areHeartbeatsEnabled: hoisted.areHeartbeatsEnabledMock, })); @@ -249,16 +249,16 @@ vi.mock("./acp-spawn-parent-stream.js", () => ({ startAcpSpawnParentStreamRelay: hoisted.startAcpSpawnParentStreamRelayMock, })); -vi.mock("./subagent-registry.js", async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock("../../subagent-registry.js", async (importOriginal) => ({ + ...(await importOriginal()), countActiveRunsForSession: hoisted.countActiveRunsForSessionMock, getSubagentRunByChildSessionKey: hoisted.getSubagentRunByChildSessionKeyMock, // ACP registration deliberately moved behind the shared spawn pipeline. registerSubagentRun: hoisted.registerSubagentRunMock, })); -vi.mock("../tasks/runtime-internal.js", async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock("../../../tasks/runtime-internal.js", async (importOriginal) => ({ + ...(await importOriginal()), listTasksForOwnerKey: hoisted.listTasksForOwnerKeyMock, })); diff --git a/src/agents/acp-spawn.ts b/src/agents/subagents/spawn/acp-spawn.ts similarity index 92% rename from src/agents/acp-spawn.ts rename to src/agents/subagents/spawn/acp-spawn.ts index a781da3019c3..a42a2da85084 100644 --- a/src/agents/acp-spawn.ts +++ b/src/agents/subagents/spawn/acp-spawn.ts @@ -1,38 +1,69 @@ /** Implements ACP subagent/session spawning, binding, limits, and parent-stream setup. */ import crypto from "node:crypto"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { AcpTurnAttachment } from "../acp/control-plane/manager.types.js"; -import { - cleanupFailedAcpSpawn, - type AcpSpawnRuntimeCloseHandle, -} from "../acp/control-plane/spawn.js"; -import { isAcpEnabledByPolicy, resolveAcpAgentPolicyError } from "../acp/policy.js"; -import { getRuntimeConfig } from "../config/config.js"; -import { resolveStorePath } from "../config/sessions/paths.js"; +import type { AcpTurnAttachment } from "../../../acp/control-plane/manager.types.js"; +import type { AcpSpawnRuntimeCloseHandle } from "../../../acp/control-plane/spawn.js"; +import { cleanupFailedAcpSpawn } from "../../../acp/control-plane/spawn.js"; +import { isAcpEnabledByPolicy, resolveAcpAgentPolicyError } from "../../../acp/policy.js"; +import { getRuntimeConfig } from "../../../config/config.js"; +import { resolveStorePath } from "../../../config/sessions/paths.js"; import { loadSessionEntryReadOnly, upsertSessionEntry, -} from "../config/sessions/session-accessor.js"; -import { buildSessionCreationStamp } from "../config/sessions/session-entry-provenance.js"; -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { callGateway } from "../gateway/call.js"; -import { formatErrorMessage } from "../infra/errors.js"; -import { resolveEventSessionRoutingPolicy } from "../infra/event-session-routing.js"; +} from "../../../config/sessions/session-accessor.js"; +import { buildSessionCreationStamp } from "../../../config/sessions/session-entry-provenance.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { callGateway } from "../../../gateway/call.js"; +import { formatErrorMessage } from "../../../infra/errors.js"; +import { resolveEventSessionRoutingPolicy } from "../../../infra/event-session-routing.js"; import { getSessionBindingService, isSessionBindingError, type SessionBindingRecord, -} from "../infra/outbound/session-binding-service.js"; -import { getGlobalHookRunner } from "../plugins/hook-runner-global.js"; +} from "../../../infra/outbound/session-binding-service.js"; +import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js"; import { normalizeAgentId, normalizeOptionalAgentId, parseAgentSessionKey, resolveAgentIdFromSessionKey, -} from "../routing/session-key.js"; -import { recordSessionCreated, recordSubagentSpawned } from "../sessions/session-state-events.js"; -import { deliveryContextFromSession } from "../utils/delivery-context.shared.js"; +} from "../../../routing/session-key.js"; +import { + recordSessionCreated, + recordSubagentSpawned, +} from "../../../sessions/session-state-events.js"; +import { deliveryContextFromSession } from "../../../utils/delivery-context.shared.js"; +import { resolveDefaultAgentId } from "../../agent-scope.js"; +import { reserveChildAdmissionSlot } from "../../child-admission.js"; +import { + findAcpUnsupportedInheritedToolAllow, + findAcpUnsupportedInheritedToolDeny, + formatAcpInheritedToolAllowError, + formatAcpInheritedToolDenyError, + inheritedToolAllowPatch, + inheritedToolDenyPatch, +} from "../../inherited-tool-deny.js"; +import { AGENT_LANE_SUBAGENT } from "../../lanes.js"; +import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js"; +import { + runSpawnPipeline, + type SpawnBackendAdapter, + summarizeSpawnError, +} from "../../spawn-pipeline.js"; +import { + mintSpawnSessionKey, + prepareSpawnThreadBinding, + resolveSpawnAdmission, + resolveSpawnMode, + resolveSpawnSandboxError, + type PreparedSpawnThreadBinding, +} from "../../spawn-plan.js"; +import { resolveSpawnedWorkspaceInheritance } from "../../spawned-context.js"; +import { + isSubagentEnvelopeSession, + resolveSubagentCapabilityStore, +} from "../../subagent-capabilities.js"; import { countUntrackedActiveAcpRunsForOwner } from "./acp-spawn-admission.js"; import { resolveAcpSpawnBootstrapDeliveryPlan, @@ -61,36 +92,6 @@ import { resolveConfiguredAcpSubagentTargetIds, resolveTargetAcpAgentId, } from "./acp-spawn-target.js"; -import { resolveDefaultAgentId } from "./agent-scope.js"; -import { reserveChildAdmissionSlot } from "./child-admission.js"; -import { - findAcpUnsupportedInheritedToolAllow, - findAcpUnsupportedInheritedToolDeny, - formatAcpInheritedToolAllowError, - formatAcpInheritedToolDenyError, - inheritedToolAllowPatch, - inheritedToolDenyPatch, -} from "./inherited-tool-deny.js"; -import { AGENT_LANE_SUBAGENT } from "./lanes.js"; -import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js"; -import { - runSpawnPipeline, - type SpawnBackendAdapter, - summarizeSpawnError, -} from "./spawn-pipeline.js"; -import { - mintSpawnSessionKey, - prepareSpawnThreadBinding, - resolveSpawnAdmission, - resolveSpawnMode, - resolveSpawnSandboxError, - type PreparedSpawnThreadBinding, -} from "./spawn-plan.js"; -import { resolveSpawnedWorkspaceInheritance } from "./spawned-context.js"; -import { - isSubagentEnvelopeSession, - resolveSubagentCapabilityStore, -} from "./subagent-capabilities.js"; import { resolveSubagentSpawnOwnership } from "./subagent-spawn-ownership.js"; import { resolveConfiguredSubagentRunTimeoutSeconds } from "./subagent-spawn-plan.js"; diff --git a/src/agents/subagent-spawn-accepted-note.ts b/src/agents/subagents/spawn/subagent-spawn-accepted-note.ts similarity index 94% rename from src/agents/subagent-spawn-accepted-note.ts rename to src/agents/subagents/spawn/subagent-spawn-accepted-note.ts index fd0f42a7bda7..f44df1fa25fa 100644 --- a/src/agents/subagent-spawn-accepted-note.ts +++ b/src/agents/subagents/spawn/subagent-spawn-accepted-note.ts @@ -3,7 +3,7 @@ * * Returns push-based completion guidance for run spawns and thread-binding guidance for session spawns. */ -import { isCronSessionKey } from "../routing/session-key.js"; +import { isCronSessionKey } from "../../../routing/session-key.js"; const SUBAGENT_SPAWN_ACCEPTED_NOTE = "Auto-announce is push-based. After spawning children, do NOT call sessions_list, sessions_history, exec sleep, or any polling tool. Track expected child session keys. Continue any independent work. If your final answer depends on child output, wait for runtime completion events to arrive as user messages and only answer after completion events for ALL required children arrive. If a child completion event arrives AFTER your final answer, reply ONLY with NO_REPLY."; diff --git a/src/agents/subagent-spawn-child-plan.ts b/src/agents/subagents/spawn/subagent-spawn-child-plan.ts similarity index 92% rename from src/agents/subagent-spawn-child-plan.ts rename to src/agents/subagents/spawn/subagent-spawn-child-plan.ts index 42c8f2c5c529..fb6a097b0286 100644 --- a/src/agents/subagent-spawn-child-plan.ts +++ b/src/agents/subagents/spawn/subagent-spawn-child-plan.ts @@ -1,19 +1,19 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { isIncognitoSessionKey } from "../routing/session-key.js"; -import { resolveUserPath } from "../utils.js"; -import { resolveAgentDir } from "./agent-scope-config.js"; -import { findModelCatalogEntry } from "./model-catalog-lookup.js"; -import { resolveDefaultModelForAgent } from "./model-selection.js"; -import { supportsModelTools } from "./model-tool-support.js"; -import { summarizeSpawnError } from "./spawn-pipeline.js"; -import { resolveSpawnSandboxError, mintSpawnSessionKey } from "./spawn-plan.js"; -import { resolveRequesterOriginForChild } from "./spawn-requester-origin.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { isIncognitoSessionKey } from "../../../routing/session-key.js"; +import { resolveUserPath } from "../../../utils.js"; +import { resolveAgentDir } from "../../agent-scope-config.js"; +import { findModelCatalogEntry } from "../../model-catalog-lookup.js"; +import { resolveDefaultModelForAgent } from "../../model-selection.js"; +import { supportsModelTools } from "../../model-tool-support.js"; +import { summarizeSpawnError } from "../../spawn-pipeline.js"; +import { resolveSpawnSandboxError, mintSpawnSessionKey } from "../../spawn-plan.js"; +import { resolveRequesterOriginForChild } from "../../spawn-requester-origin.js"; import { mapToolContextToSpawnedRunMetadata, resolveSpawnedWorkspaceInheritance, -} from "./spawned-context.js"; -import type { SubagentLaunchAuthorization } from "./subagent-launch-authorization.js"; +} from "../../spawned-context.js"; +import type { SubagentLaunchAuthorization } from "../../subagent-launch-authorization.js"; import type { SpawnSubagentContext, SpawnSubagentParams, diff --git a/src/agents/subagent-spawn-cleanup.test.ts b/src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts similarity index 100% rename from src/agents/subagent-spawn-cleanup.test.ts rename to src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts diff --git a/src/agents/subagent-spawn-cleanup.ts b/src/agents/subagents/spawn/subagent-spawn-cleanup.ts similarity index 95% rename from src/agents/subagent-spawn-cleanup.ts rename to src/agents/subagents/spawn/subagent-spawn-cleanup.ts index b01a5d57a1c8..d911f3b5ad6a 100644 --- a/src/agents/subagent-spawn-cleanup.ts +++ b/src/agents/subagents/spawn/subagent-spawn-cleanup.ts @@ -1,7 +1,7 @@ import { promises as fs } from "node:fs"; -import type { callGateway } from "../gateway/call.js"; -import { isFastTestRuntimeEnv } from "../infra/env.js"; -import { deleteSubagentSessionForCleanup } from "./subagent-session-cleanup.js"; +import type { callGateway } from "../../../gateway/call.js"; +import { isFastTestRuntimeEnv } from "../../../infra/env.js"; +import { deleteSubagentSessionForCleanup } from "../../subagent-session-cleanup.js"; import { callSubagentGateway } from "./subagent-spawn-gateway.js"; const SUBAGENT_CONTROL_GATEWAY_TIMEOUT_MS = 60_000; diff --git a/src/agents/subagent-spawn-context.ts b/src/agents/subagents/spawn/subagent-spawn-context.ts similarity index 94% rename from src/agents/subagent-spawn-context.ts rename to src/agents/subagents/spawn/subagent-spawn-context.ts index 41ec0cc2af7d..df7890d08e3f 100644 --- a/src/agents/subagent-spawn-context.ts +++ b/src/agents/subagents/spawn/subagent-spawn-context.ts @@ -1,9 +1,9 @@ import { finiteSecondsToTimerSafeMilliseconds } from "@openclaw/normalization-core/number-coercion"; -import { resolveThreadBindingSpawnPolicy } from "../channels/thread-bindings-policy.js"; -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { SubagentSpawnPreparation } from "../context-engine/types.js"; -import { summarizeSpawnError } from "./spawn-pipeline.js"; +import { resolveThreadBindingSpawnPolicy } from "../../../channels/thread-bindings-policy.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { SubagentSpawnPreparation } from "../../../context-engine/types.js"; +import { summarizeSpawnError } from "../../spawn-pipeline.js"; import { getSubagentSpawnDeps } from "./subagent-spawn-deps.js"; import { resolveGatewaySessionStoreTarget } from "./subagent-spawn.runtime.js"; import type { SpawnSubagentContextMode } from "./subagent-spawn.types.js"; diff --git a/src/agents/subagent-spawn-contract.ts b/src/agents/subagents/spawn/subagent-spawn-contract.ts similarity index 97% rename from src/agents/subagent-spawn-contract.ts rename to src/agents/subagents/spawn/subagent-spawn-contract.ts index ea592f1d785a..7947873a167c 100644 --- a/src/agents/subagent-spawn-contract.ts +++ b/src/agents/subagents/spawn/subagent-spawn-contract.ts @@ -1,4 +1,4 @@ -import type { FastMode } from "../shared/fast-mode.js"; +import type { FastMode } from "../../../shared/fast-mode.js"; import type { SpawnSubagentContextMode, SpawnSubagentMode, diff --git a/src/agents/subagent-spawn-deps.ts b/src/agents/subagents/spawn/subagent-spawn-deps.ts similarity index 95% rename from src/agents/subagent-spawn-deps.ts rename to src/agents/subagents/spawn/subagent-spawn-deps.ts index cb502f0c91e4..e5e6b5c44d14 100644 --- a/src/agents/subagent-spawn-deps.ts +++ b/src/agents/subagents/spawn/subagent-spawn-deps.ts @@ -1,4 +1,4 @@ -import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js"; +import type { SubagentLifecycleHookRunner } from "../../../plugins/hooks.js"; import { callGateway, dispatchGatewayMethodInProcess, diff --git a/src/agents/subagent-spawn-gateway.ts b/src/agents/subagents/spawn/subagent-spawn-gateway.ts similarity index 93% rename from src/agents/subagent-spawn-gateway.ts rename to src/agents/subagents/spawn/subagent-spawn-gateway.ts index e7cb41ade690..8be364008ff1 100644 --- a/src/agents/subagent-spawn-gateway.ts +++ b/src/agents/subagents/spawn/subagent-spawn-gateway.ts @@ -1,6 +1,6 @@ -import type { SubagentLaunchAuthorization } from "./subagent-launch-authorization.js"; -import { applySubagentLaunchAuthorization } from "./subagent-launch-authorization.js"; -import { resolveSubagentRunTimerDelayMs } from "./subagent-run-timeout.js"; +import type { SubagentLaunchAuthorization } from "../../subagent-launch-authorization.js"; +import { applySubagentLaunchAuthorization } from "../../subagent-launch-authorization.js"; +import { resolveSubagentRunTimerDelayMs } from "../../subagent-run-timeout.js"; import { getSubagentSpawnDeps } from "./subagent-spawn-deps.js"; import { ADMIN_SCOPE, diff --git a/src/agents/subagent-spawn-launch-request.ts b/src/agents/subagents/spawn/subagent-spawn-launch-request.ts similarity index 92% rename from src/agents/subagent-spawn-launch-request.ts rename to src/agents/subagents/spawn/subagent-spawn-launch-request.ts index 85ad15cd8f22..64e8787ce85b 100644 --- a/src/agents/subagent-spawn-launch-request.ts +++ b/src/agents/subagents/spawn/subagent-spawn-launch-request.ts @@ -1,8 +1,8 @@ -import { stringifyRouteThreadId } from "../plugin-sdk/channel-route.js"; -import type { BootstrapContextMode } from "./bootstrap-files.js"; -import { normalizeSpawnedRunMetadata } from "./spawned-context.js"; -import { buildSubagentInitialUserMessage } from "./subagent-initial-user-message.js"; -import type { SubagentLaunchAuthorization } from "./subagent-launch-authorization.js"; +import { stringifyRouteThreadId } from "../../../plugin-sdk/channel-route.js"; +import type { BootstrapContextMode } from "../../bootstrap-files.js"; +import { normalizeSpawnedRunMetadata } from "../../spawned-context.js"; +import { buildSubagentInitialUserMessage } from "../../subagent-initial-user-message.js"; +import type { SubagentLaunchAuthorization } from "../../subagent-launch-authorization.js"; import { resolveSubagentAgentGatewayTimeoutMs } from "./subagent-spawn-gateway.js"; import { AGENT_LANE_SUBAGENT } from "./subagent-spawn.runtime.js"; import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; diff --git a/src/agents/subagent-spawn-lifecycle.ts b/src/agents/subagents/spawn/subagent-spawn-lifecycle.ts similarity index 93% rename from src/agents/subagent-spawn-lifecycle.ts rename to src/agents/subagents/spawn/subagent-spawn-lifecycle.ts index 290ef3ce6c5f..c04303150eb1 100644 --- a/src/agents/subagent-spawn-lifecycle.ts +++ b/src/agents/subagents/spawn/subagent-spawn-lifecycle.ts @@ -1,5 +1,5 @@ -import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js"; -import type { DeliveryContext } from "../utils/delivery-context.types.js"; +import type { SubagentLifecycleHookRunner } from "../../../plugins/hooks.js"; +import type { DeliveryContext } from "../../../utils/delivery-context.types.js"; import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; export function createSubagentSpawnLifecycleEmitter(params: { diff --git a/src/agents/subagent-spawn-ownership.ts b/src/agents/subagents/spawn/subagent-spawn-ownership.ts similarity index 93% rename from src/agents/subagent-spawn-ownership.ts rename to src/agents/subagents/spawn/subagent-spawn-ownership.ts index 592a97045e3a..b6be1187bd00 100644 --- a/src/agents/subagent-spawn-ownership.ts +++ b/src/agents/subagents/spawn/subagent-spawn-ownership.ts @@ -3,12 +3,12 @@ * * Resolves which session controls spawn state, thread binding, and completion delivery. */ -import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { resolveDisplaySessionKey, resolveInternalSessionKey, resolveMainSessionAlias, -} from "./tools/sessions-helpers.js"; +} from "../../tools/sessions-helpers.js"; type SubagentSpawnOwnership = { controllerSessionKey: string; diff --git a/src/agents/subagent-spawn-plan.ts b/src/agents/subagents/spawn/subagent-spawn-plan.ts similarity index 94% rename from src/agents/subagent-spawn-plan.ts rename to src/agents/subagents/spawn/subagent-spawn-plan.ts index 657da958cf77..ab878d0b798b 100644 --- a/src/agents/subagent-spawn-plan.ts +++ b/src/agents/subagents/spawn/subagent-spawn-plan.ts @@ -3,14 +3,14 @@ * * Resolves model, thinking, and timeout choices before the sessions_spawn executor launches work. */ -import { formatThinkingLevels } from "../auto-reply/thinking.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { FastMode } from "../shared/fast-mode.js"; +import { formatThinkingLevels } from "../../../auto-reply/thinking.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { FastMode } from "../../../shared/fast-mode.js"; import { resolveDefaultModelForAgent, resolveSubagentConfiguredModelSelection, resolveSubagentSpawnModelSelection, -} from "./model-selection.js"; +} from "../../model-selection.js"; import { resolveSubagentThinkingOverride } from "./subagent-spawn-thinking.js"; /** Splits a provider/model ref while preserving model-only refs. */ diff --git a/src/agents/subagent-spawn-request.ts b/src/agents/subagents/spawn/subagent-spawn-request.ts similarity index 93% rename from src/agents/subagent-spawn-request.ts rename to src/agents/subagents/spawn/subagent-spawn-request.ts index bd6b8cd8bb69..d403b4e2cbe7 100644 --- a/src/agents/subagent-spawn-request.ts +++ b/src/agents/subagents/spawn/subagent-spawn-request.ts @@ -1,12 +1,20 @@ import crypto from "node:crypto"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js"; -import { isValidAgentId, normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js"; -import { listAgentIds } from "./agent-scope-config.js"; -import { reserveChildAdmissionSlot } from "./child-admission.js"; -import { resolveSpawnAdmission, resolveSpawnMode } from "./spawn-plan.js"; -import { listSwarmRunsForGroup } from "./subagent-registry.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { SubagentLifecycleHookRunner } from "../../../plugins/hooks.js"; +import { + isValidAgentId, + normalizeAgentId, + parseAgentSessionKey, +} from "../../../routing/session-key.js"; +import { listAgentIds } from "../../agent-scope-config.js"; +import { reserveChildAdmissionSlot } from "../../child-admission.js"; +import { resolveSpawnAdmission, resolveSpawnMode } from "../../spawn-plan.js"; +import { listSwarmRunsForGroup } from "../../subagent-registry.js"; +import { normalizeSubagentTaskName } from "../../subagent-task-name.js"; +import { resolveSwarmConfig } from "../swarm/swarm-config.js"; +import { validateStructuredOutputSchema } from "../swarm/swarm-output-schema.js"; +import { reserveSwarmRun } from "../swarm/swarm-scheduler.js"; import { resolveSubagentContextMode } from "./subagent-spawn-context.js"; import type { SpawnSubagentContext, @@ -18,10 +26,6 @@ import { resolveSubagentSpawnOwnership } from "./subagent-spawn-ownership.js"; import { resolveConfiguredSubagentRunTimeoutSeconds } from "./subagent-spawn-plan.js"; import { loadSubagentConfig } from "./subagent-spawn-session-patch.js"; import { resolveInternalSessionKey, resolveMainSessionAlias } from "./subagent-spawn.runtime.js"; -import { normalizeSubagentTaskName } from "./subagent-task-name.js"; -import { resolveSwarmConfig } from "./subagents/swarm/swarm-config.js"; -import { validateStructuredOutputSchema } from "./subagents/swarm/swarm-output-schema.js"; -import { reserveSwarmRun } from "./subagents/swarm/swarm-scheduler.js"; type ResolvedSubagentSpawnRequest = { request: { diff --git a/src/agents/subagent-spawn-requester-prefs.ts b/src/agents/subagents/spawn/subagent-spawn-requester-prefs.ts similarity index 90% rename from src/agents/subagent-spawn-requester-prefs.ts rename to src/agents/subagents/spawn/subagent-spawn-requester-prefs.ts index 6c5266cf944a..583756ff592c 100644 --- a/src/agents/subagent-spawn-requester-prefs.ts +++ b/src/agents/subagents/spawn/subagent-spawn-requester-prefs.ts @@ -1,13 +1,13 @@ -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { FastMode } from "../shared/fast-mode.js"; -import { resolveFastModeState } from "./fast-mode.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { FastMode } from "../../../shared/fast-mode.js"; +import { resolveFastModeState } from "../../fast-mode.js"; import { normalizeStoredOverrideModel, resolveDefaultModelForAgent, resolvePersistedSelectedModelRef, -} from "./model-selection.js"; -import { resolveThinkingDefault } from "./model-thinking-default.js"; +} from "../../model-selection.js"; +import { resolveThinkingDefault } from "../../model-thinking-default.js"; import { loadSessionEntry, resolveAgentConfig, diff --git a/src/agents/subagent-spawn-session-patch.ts b/src/agents/subagents/spawn/subagent-spawn-session-patch.ts similarity index 95% rename from src/agents/subagent-spawn-session-patch.ts rename to src/agents/subagents/spawn/subagent-spawn-session-patch.ts index c43ab3ac1775..c0d8edaf56a7 100644 --- a/src/agents/subagent-spawn-session-patch.ts +++ b/src/agents/subagents/spawn/subagent-spawn-session-patch.ts @@ -1,15 +1,15 @@ import { randomUUID } from "node:crypto"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { buildSessionCreationStamp } from "../config/sessions/session-entry-provenance.js"; -import type { SessionEntry } from "../config/sessions/types.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { resolveIncognitoOpenClawAgentSqlitePath } from "../state/openclaw-agent-db.js"; +import { buildSessionCreationStamp } from "../../../config/sessions/session-entry-provenance.js"; +import type { SessionEntry } from "../../../config/sessions/types.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { resolveIncognitoOpenClawAgentSqlitePath } from "../../../state/openclaw-agent-db.js"; import { inheritedToolAllowPatch, inheritedToolDenyPatch, normalizeInheritedToolAllowlist, normalizeInheritedToolDenylist, -} from "./inherited-tool-deny.js"; +} from "../../inherited-tool-deny.js"; import { getSubagentSpawnDeps } from "./subagent-spawn-deps.js"; import { splitModelRef } from "./subagent-spawn-plan.js"; import { resolveGatewaySessionStoreTarget, upsertSessionEntry } from "./subagent-spawn.runtime.js"; diff --git a/src/agents/subagent-spawn-thinking.ts b/src/agents/subagents/spawn/subagent-spawn-thinking.ts similarity index 93% rename from src/agents/subagent-spawn-thinking.ts rename to src/agents/subagents/spawn/subagent-spawn-thinking.ts index 91ae3ab727d3..2f7f9715ae8e 100644 --- a/src/agents/subagent-spawn-thinking.ts +++ b/src/agents/subagents/spawn/subagent-spawn-thinking.ts @@ -3,8 +3,8 @@ * this helper to patch the child session without leaking invalid caller input. */ import { asOptionalObjectRecord } from "@openclaw/normalization-core/record-coerce"; -import { normalizeThinkLevel } from "../auto-reply/thinking.shared.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { normalizeThinkLevel } from "../../../auto-reply/thinking.shared.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; function readString(value: Record, key: string): string | undefined { const raw = value[key]; diff --git a/src/agents/subagent-spawn-thread-binding.ts b/src/agents/subagents/spawn/subagent-spawn-thread-binding.ts similarity index 86% rename from src/agents/subagent-spawn-thread-binding.ts rename to src/agents/subagents/spawn/subagent-spawn-thread-binding.ts index b20bb6d299c0..7a69f173a14b 100644 --- a/src/agents/subagent-spawn-thread-binding.ts +++ b/src/agents/subagents/spawn/subagent-spawn-thread-binding.ts @@ -1,16 +1,19 @@ -import { routeFromBindingRecord, routeToDeliveryFields } from "../channels/route-projection.js"; +import { + routeFromBindingRecord, + routeToDeliveryFields, +} from "../../../channels/route-projection.js"; import { resolveThreadBindingIntroText, resolveThreadBindingThreadName, -} from "../channels/thread-bindings-messages.js"; +} from "../../../channels/thread-bindings-messages.js"; import { resolveThreadBindingIdleTimeoutMsForChannel, resolveThreadBindingMaxAgeMsForChannel, -} from "../channels/thread-bindings-policy.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { DeliveryContext } from "../utils/delivery-context.types.js"; -import { summarizeSpawnError } from "./spawn-pipeline.js"; -import { prepareSpawnThreadBinding } from "./spawn-plan.js"; +} from "../../../channels/thread-bindings-policy.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { DeliveryContext } from "../../../utils/delivery-context.types.js"; +import { summarizeSpawnError } from "../../spawn-pipeline.js"; +import { prepareSpawnThreadBinding } from "../../spawn-plan.js"; import { getSessionBindingService } from "./subagent-spawn.runtime.js"; import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; diff --git a/src/agents/subagent-spawn.attachments.test.ts b/src/agents/subagents/spawn/subagent-spawn.attachments.test.ts similarity index 99% rename from src/agents/subagent-spawn.attachments.test.ts rename to src/agents/subagents/spawn/subagent-spawn.attachments.test.ts index 56af28a2acf7..8f5870deeea0 100644 --- a/src/agents/subagent-spawn.attachments.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.attachments.test.ts @@ -4,7 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { withEnvAsync } from "../test-utils/env.js"; +import { withEnvAsync } from "../../../test-utils/env.js"; import { createSubagentSpawnTestConfig, loadSubagentSpawnModuleForTest, diff --git a/src/agents/subagent-spawn.context.test.ts b/src/agents/subagents/spawn/subagent-spawn.context.test.ts similarity index 100% rename from src/agents/subagent-spawn.context.test.ts rename to src/agents/subagents/spawn/subagent-spawn.context.test.ts diff --git a/src/agents/subagent-spawn.depth-limits.test.ts b/src/agents/subagents/spawn/subagent-spawn.depth-limits.test.ts similarity index 100% rename from src/agents/subagent-spawn.depth-limits.test.ts rename to src/agents/subagents/spawn/subagent-spawn.depth-limits.test.ts diff --git a/src/agents/subagent-spawn.in-process-gateway.test.ts b/src/agents/subagents/spawn/subagent-spawn.in-process-gateway.test.ts similarity index 93% rename from src/agents/subagent-spawn.in-process-gateway.test.ts rename to src/agents/subagents/spawn/subagent-spawn.in-process-gateway.test.ts index 9e8c389a3f2c..17d9a0de101a 100644 --- a/src/agents/subagent-spawn.in-process-gateway.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.in-process-gateway.test.ts @@ -6,34 +6,34 @@ import { clearConfigCache, clearRuntimeConfigSnapshot, getRuntimeConfig, -} from "../config/config.js"; -import { prepareAgentRequestPreflight } from "../gateway/agent-turn/agent-request-preflight.js"; -import { createAgentTurnIo } from "../gateway/agent-turn/io.js"; +} from "../../../config/config.js"; +import { prepareAgentRequestPreflight } from "../../../gateway/agent-turn/agent-request-preflight.js"; +import { createAgentTurnIo } from "../../../gateway/agent-turn/io.js"; import type { GatewayRequestContext, GatewayRequestOptions, -} from "../gateway/server-methods/types.js"; -import { createSyntheticPluginRuntimeClient } from "../gateway/server-plugin-runtime-client.js"; +} from "../../../gateway/server-methods/types.js"; +import { createSyntheticPluginRuntimeClient } from "../../../gateway/server-plugin-runtime-client.js"; import { clearFallbackGatewayContext, type dispatchGatewayMethodInProcess, -} from "../gateway/server-plugins.js"; -import { withPluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; +} from "../../../gateway/server-plugins.js"; +import { withPluginRuntimeGatewayRequestScope } from "../../../plugins/runtime/gateway-request-scope.js"; import { isGatewaySubordinateWorkAdmissionClosed, resetGatewayWorkAdmission, tryBeginGatewayRootWorkAdmission, -} from "../process/gateway-work-admission.js"; -import { captureEnv, setTestEnvValue } from "../test-utils/env.js"; -import { subagentRuns } from "./subagent-registry-memory.js"; -import { markSubagentRunTerminated } from "./subagent-registry.js"; +} from "../../../process/gateway-work-admission.js"; +import { captureEnv, setTestEnvValue } from "../../../test-utils/env.js"; +import { subagentRuns } from "../../subagent-registry-memory.js"; +import { markSubagentRunTerminated } from "../../subagent-registry.js"; import { resetSubagentRegistryForTests, testing as subagentRegistryTesting, -} from "./subagent-registry.test-helpers.js"; +} from "../../subagent-registry.test-helpers.js"; +import { testing as swarmSchedulerTesting } from "../swarm/swarm-scheduler.test-support.js"; import { spawnSubagentDirect } from "./subagent-spawn.js"; import { testing as subagentSpawnTesting } from "./subagent-spawn.test-support.js"; -import { testing as swarmSchedulerTesting } from "./subagents/swarm/swarm-scheduler.test-support.js"; const envSnapshot = captureEnv(["OPENCLAW_CONFIG_PATH", "OPENCLAW_STATE_DIR"]); let stateDir = ""; diff --git a/src/agents/subagent-spawn.mode-session-diagnostics.test.ts b/src/agents/subagents/spawn/subagent-spawn.mode-session-diagnostics.test.ts similarity index 93% rename from src/agents/subagent-spawn.mode-session-diagnostics.test.ts rename to src/agents/subagents/spawn/subagent-spawn.mode-session-diagnostics.test.ts index 7daebf97d49e..2538a8f8bed4 100644 --- a/src/agents/subagent-spawn.mode-session-diagnostics.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.mode-session-diagnostics.test.ts @@ -10,7 +10,7 @@ import { describe('spawnSubagentDirect mode="session" diagnostics (#67400)', () => { const callGatewayMock = vi.fn(); let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; - let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; + let resetSubagentRegistryForTests: typeof import("../../subagent-registry.test-helpers.js").resetSubagentRegistryForTests; beforeEach(async () => { callGatewayMock.mockReset(); @@ -68,7 +68,7 @@ describe('spawnSubagentDirect mode="session" diagnostics (#67400)', () => { describe('spawnSubagentDirect mode="session" with thread binding-capable channels (#67400)', () => { const callGatewayMock = vi.fn(); let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; - let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; + let resetSubagentRegistryForTests: typeof import("../../subagent-registry.test-helpers.js").resetSubagentRegistryForTests; beforeEach(async () => { callGatewayMock.mockReset(); diff --git a/src/agents/subagent-spawn.model-session.test.ts b/src/agents/subagents/spawn/subagent-spawn.model-session.test.ts similarity index 97% rename from src/agents/subagent-spawn.model-session.test.ts rename to src/agents/subagents/spawn/subagent-spawn.model-session.test.ts index f23f33c4da45..69d94663effd 100644 --- a/src/agents/subagent-spawn.model-session.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.model-session.test.ts @@ -13,7 +13,7 @@ import { const callGatewayMock = vi.fn(); const updateSessionStoreMock = vi.fn(); -let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; +let resetSubagentRegistryForTests: typeof import("../../subagent-registry.test-helpers.js").resetSubagentRegistryForTests; let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; describe("spawnSubagentDirect runtime model persistence", () => { diff --git a/src/agents/subagents/spawn/subagent-spawn.runtime.ts b/src/agents/subagents/spawn/subagent-spawn.runtime.ts new file mode 100644 index 000000000000..0e851eec084f --- /dev/null +++ b/src/agents/subagents/spawn/subagent-spawn.runtime.ts @@ -0,0 +1,39 @@ +/** + * Runtime dependency barrel for subagent spawning. Keeping these imports in a + * single module lets spawn tests replace runtime seams without loading the + * entire gateway/channel stack. + */ +export { getRuntimeConfig } from "../../../config/config.js"; +export { + loadSessionEntryReadOnly as loadSessionEntry, + upsertSessionEntry, +} from "../../../config/sessions/session-accessor.js"; +export { forkSessionEntryFromParent } from "../../../auto-reply/reply/session-fork.js"; +export { ensureContextEnginesInitialized } from "../../../context-engine/init.js"; +export { resolveContextEngine } from "../../../context-engine/registry.js"; +export { callGateway } from "../../../gateway/call.js"; +export { + dispatchGatewayMethodInProcess, + hasInProcessGatewayContext, +} from "../../../gateway/server-plugins.js"; +export { + ADMIN_SCOPE, + resolveLeastPrivilegeOperatorScopesForMethod, +} from "../../../gateway/method-scopes.js"; +export { getSessionBindingService } from "../../../infra/outbound/session-binding-service.js"; +export { resolveGatewaySessionStoreTarget } from "../../../gateway/session-utils.js"; +export { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js"; +export { emitSessionLifecycleEvent } from "../../../sessions/session-lifecycle-events.js"; +export { + mergeDeliveryContext, + normalizeDeliveryContext, +} from "../../../utils/delivery-context.shared.js"; +export { resolveAgentConfig } from "../../agent-scope.js"; +export { AGENT_LANE_SUBAGENT } from "../../lanes.js"; +export { loadPreparedModelCatalog } from "../../prepared-model-catalog.js"; +export { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js"; +export { buildSubagentSystemPrompt } from "../../subagent-system-prompt.js"; +export { + resolveInternalSessionKey, + resolveMainSessionAlias, +} from "../../tools/sessions-helpers.js"; diff --git a/src/agents/subagent-spawn.test-helpers.ts b/src/agents/subagents/spawn/subagent-spawn.test-helpers.ts similarity index 98% rename from src/agents/subagent-spawn.test-helpers.ts rename to src/agents/subagents/spawn/subagent-spawn.test-helpers.ts index c2c6e08292be..a9ef3ebb0241 100644 --- a/src/agents/subagent-spawn.test-helpers.ts +++ b/src/agents/subagents/spawn/subagent-spawn.test-helpers.ts @@ -3,8 +3,8 @@ import os from "node:os"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { expect, vi } from "vitest"; -import { resolveLeastPrivilegeOperatorScopesForMethod } from "../gateway/method-scopes.js"; -import type { SubagentLifecycleHookRunner } from "../plugins/hooks.js"; +import { resolveLeastPrivilegeOperatorScopesForMethod } from "../../../gateway/method-scopes.js"; +import type { SubagentLifecycleHookRunner } from "../../../plugins/hooks.js"; type MockFn = (...args: unknown[]) => unknown; type MockImplementationTarget = { @@ -211,7 +211,7 @@ export async function loadSubagentSpawnModuleForTest(params: { const resetSubagentRegistryForTests = vi.fn(); - vi.doMock("./provider-model-normalization.runtime.js", () => ({ + vi.doMock("../../provider-model-normalization.runtime.js", () => ({ normalizeProviderModelIdWithRuntime: () => undefined, })); @@ -383,11 +383,11 @@ export async function loadSubagentSpawnModuleForTest(params: { ...createDefaultSessionHelperMocks(), })); - vi.doMock("./subagent-depth.js", () => ({ + vi.doMock("../../subagent-depth.js", () => ({ getSubagentDepthFromSessionStore: params.getSubagentDepthFromSessionStore ?? (() => 0), })); - vi.doMock("./subagent-registry.js", () => ({ + vi.doMock("../../subagent-registry.js", () => ({ completeCollectorLaunchCleanup: params.completeCollectorLaunchCleanupMock ?? vi.fn(), countActiveRunsForSession: params.countActiveRunsForSession ?? (() => 0), listSwarmRunsForGroup: params.listSwarmRunsForGroup ?? vi.fn(() => []), diff --git a/src/agents/subagent-spawn.test-support.ts b/src/agents/subagents/spawn/subagent-spawn.test-support.ts similarity index 89% rename from src/agents/subagent-spawn.test-support.ts rename to src/agents/subagents/spawn/subagent-spawn.test-support.ts index 28802ea4b8d8..72a3d303bb9a 100644 --- a/src/agents/subagent-spawn.test-support.ts +++ b/src/agents/subagents/spawn/subagent-spawn.test-support.ts @@ -16,7 +16,7 @@ type SpawnDeps = Omit< >, "getGlobalHookRunner" > & { - getGlobalHookRunner: () => import("../plugins/hooks.js").SubagentLifecycleHookRunner | null; + getGlobalHookRunner: () => import("../../../plugins/hooks.js").SubagentLifecycleHookRunner | null; }; type Testing = { diff --git a/src/agents/subagent-spawn.test.ts b/src/agents/subagents/spawn/subagent-spawn.test.ts similarity index 99% rename from src/agents/subagent-spawn.test.ts rename to src/agents/subagents/spawn/subagent-spawn.test.ts index f04f3e0806db..44e18db65fce 100644 --- a/src/agents/subagent-spawn.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.test.ts @@ -3,16 +3,16 @@ import os from "node:os"; // persistence, registry registration, and lifecycle event emission. import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { resolveIncognitoOpenClawAgentSqlitePath } from "../state/openclaw-agent-db.paths.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import { resolveIncognitoOpenClawAgentSqlitePath } from "../../../state/openclaw-agent-db.paths.js"; +import { installAcceptedSubagentGatewayMock } from "../../test-helpers/subagent-gateway.js"; +import { testing as swarmSchedulerTesting } from "../swarm/swarm-scheduler.test-support.js"; import { createSubagentSpawnTestConfig, expectPersistedRuntimeModel, installSessionStoreCaptureMock, loadSubagentSpawnModuleForTest, } from "./subagent-spawn.test-helpers.js"; -import { testing as swarmSchedulerTesting } from "./subagents/swarm/swarm-scheduler.test-support.js"; -import { installAcceptedSubagentGatewayMock } from "./test-helpers/subagent-gateway.js"; const hoisted = vi.hoisted(() => ({ callGatewayMock: vi.fn(), @@ -33,7 +33,7 @@ const hoisted = vi.hoisted(() => ({ configOverride: {} as Record, })); -let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; +let resetSubagentRegistryForTests: typeof import("../../subagent-registry.test-helpers.js").resetSubagentRegistryForTests; let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; function createConfigOverride(overrides?: Record) { @@ -881,7 +881,7 @@ describe("spawnSubagentDirect seam flow", () => { }); it("shares pending child capacity between native and visible spawn paths", async () => { - const { maybeSpawnVisibleSession } = await import("./tools/sessions-spawn-visible.js"); + const { maybeSpawnVisibleSession } = await import("../../tools/sessions-spawn-visible.js"); hoisted.configOverride = createConfigOverride({ agents: { defaults: { diff --git a/src/agents/subagent-spawn.thread-binding.test.ts b/src/agents/subagents/spawn/subagent-spawn.thread-binding.test.ts similarity index 95% rename from src/agents/subagent-spawn.thread-binding.test.ts rename to src/agents/subagents/spawn/subagent-spawn.thread-binding.test.ts index 05a9d45f4919..9fbd0e4d1c17 100644 --- a/src/agents/subagent-spawn.thread-binding.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.thread-binding.test.ts @@ -2,12 +2,12 @@ // account selection, and completion routing for channel thread spawns. import os from "node:os"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { installAcceptedSubagentGatewayMock } from "../../test-helpers/subagent-gateway.js"; import { createSubagentSpawnTestConfig, installSessionStoreCaptureMock, loadSubagentSpawnModuleForTest, } from "./subagent-spawn.test-helpers.js"; -import { installAcceptedSubagentGatewayMock } from "./test-helpers/subagent-gateway.js"; const hoisted = vi.hoisted(() => ({ callGatewayMock: vi.fn(), @@ -45,10 +45,12 @@ function firstRegisteredSubagentRun(): { describe("spawnSubagentDirect thread binding delivery", () => { type SpawnModule = Awaited>; - type SetActivePluginRegistry = typeof import("../plugins/runtime.js").setActivePluginRegistry; + type SetActivePluginRegistry = + typeof import("../../../plugins/runtime.js").setActivePluginRegistry; type CreateChannelTestPluginBase = - typeof import("../test-utils/channel-plugins.js").createChannelTestPluginBase; - type CreateTestRegistry = typeof import("../test-utils/channel-plugins.js").createTestRegistry; + typeof import("../../../test-utils/channel-plugins.js").createChannelTestPluginBase; + type CreateTestRegistry = + typeof import("../../../test-utils/channel-plugins.js").createTestRegistry; type SessionBindingService = NonNullable< Parameters[0]["getSessionBindingService"] >; @@ -78,11 +80,11 @@ describe("spawnSubagentDirect thread binding delivery", () => { resolveConversationDeliveryTarget: (params) => currentDeliveryTargetResolver(params), })); ({ setActivePluginRegistry: setActivePluginRegistryForTest } = - await import("../plugins/runtime.js")); + await import("../../../plugins/runtime.js")); ({ createChannelTestPluginBase: createChannelTestPluginBaseForTest, createTestRegistry: createTestRegistryForTest, - } = await import("../test-utils/channel-plugins.js")); + } = await import("../../../test-utils/channel-plugins.js")); }); function installChannelRouteProjectionPluginsForTest() { diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagents/spawn/subagent-spawn.ts similarity index 97% rename from src/agents/subagent-spawn.ts rename to src/agents/subagents/spawn/subagent-spawn.ts index 0ae0647cc925..178e3cbfdd06 100644 --- a/src/agents/subagent-spawn.ts +++ b/src/agents/subagents/spawn/subagent-spawn.ts @@ -5,28 +5,32 @@ */ import { promises as fs } from "node:fs"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { isAcpRuntimeSpawnAvailable } from "../acp/runtime/availability.js"; -import type { SubagentSpawnPreparation } from "../context-engine/types.js"; -import { listRegisteredPluginAgentPromptGuidance } from "../plugins/command-registry-state.js"; +import { isAcpRuntimeSpawnAvailable } from "../../../acp/runtime/availability.js"; +import type { SubagentSpawnPreparation } from "../../../context-engine/types.js"; +import { listRegisteredPluginAgentPromptGuidance } from "../../../plugins/command-registry-state.js"; import { GatewayDrainingError, runWithGatewayIndependentRootWorkContinuation, -} from "../process/gateway-work-admission.js"; -import { recordSessionCreated, recordSubagentSpawned } from "../sessions/session-state-events.js"; +} from "../../../process/gateway-work-admission.js"; +import { + recordSessionCreated, + recordSubagentSpawned, +} from "../../../sessions/session-state-events.js"; import { runSpawnPipeline, type SpawnBackendAdapter, summarizeSpawnError, -} from "./spawn-pipeline.js"; +} from "../../spawn-pipeline.js"; import { materializeSubagentAttachments, type SubagentAttachmentReceiptFile, -} from "./subagent-attachments.js"; +} from "../../subagent-attachments.js"; import { completeCollectorLaunchCleanup, settleFailedQueuedSubagentLaunch, startQueuedSubagentRun, -} from "./subagent-registry.js"; +} from "../../subagent-registry.js"; +import { activateSwarmRun, removeQueuedSwarmRun } from "../swarm/swarm-scheduler.js"; import { resolveSubagentSpawnAcceptedNote } from "./subagent-spawn-accepted-note.js"; import { resolveSubagentChildPlan } from "./subagent-spawn-child-plan.js"; import { @@ -63,7 +67,6 @@ import { emitSessionLifecycleEvent, mergeDeliveryContext, } from "./subagent-spawn.runtime.js"; -import { activateSwarmRun, removeQueuedSwarmRun } from "./subagents/swarm/swarm-scheduler.js"; export { SUBAGENT_SPAWN_CONTEXT_MODES, SUBAGENT_SPAWN_MODES } from "./subagent-spawn.types.js"; diff --git a/src/agents/subagent-spawn.types.ts b/src/agents/subagents/spawn/subagent-spawn.types.ts similarity index 100% rename from src/agents/subagent-spawn.types.ts rename to src/agents/subagents/spawn/subagent-spawn.types.ts diff --git a/src/agents/subagent-spawn.workspace.test.ts b/src/agents/subagents/spawn/subagent-spawn.workspace.test.ts similarity index 99% rename from src/agents/subagent-spawn.workspace.test.ts rename to src/agents/subagents/spawn/subagent-spawn.workspace.test.ts index f2930501298a..3edf17a9010b 100644 --- a/src/agents/subagent-spawn.workspace.test.ts +++ b/src/agents/subagents/spawn/subagent-spawn.workspace.test.ts @@ -63,7 +63,7 @@ const hoisted = vi.hoisted(() => ({ })); let spawnSubagentDirect: typeof import("./subagent-spawn.js").spawnSubagentDirect; -let resetSubagentRegistryForTests: typeof import("./subagent-registry.test-helpers.js").resetSubagentRegistryForTests; +let resetSubagentRegistryForTests: typeof import("../../subagent-registry.test-helpers.js").resetSubagentRegistryForTests; function createConfigOverride(overrides?: Record) { return createSubagentSpawnTestConfig("/tmp/workspace-main", { diff --git a/src/agents/tools/sessions-spawn-tool.test.ts b/src/agents/tools/sessions-spawn-tool.test.ts index bd9580db677b..0747646273e3 100644 --- a/src/agents/tools/sessions-spawn-tool.test.ts +++ b/src/agents/tools/sessions-spawn-tool.test.ts @@ -29,13 +29,13 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("../subagent-spawn.js", () => ({ +vi.mock("../subagents/spawn/subagent-spawn.js", () => ({ SUBAGENT_SPAWN_CONTEXT_MODES: ["isolated", "fork"], SUBAGENT_SPAWN_MODES: ["run", "session"], spawnSubagentDirect: (...args: unknown[]) => hoisted.spawnSubagentDirectMock(...args), })); -vi.mock("../acp-spawn.js", () => ({ +vi.mock("../subagents/spawn/acp-spawn.js", () => ({ ACP_SPAWN_MODES: ["run", "session"], ACP_SPAWN_STREAM_TARGETS: ["parent"], isSpawnAcpAcceptedResult: (result: { status?: string }) => result?.status === "accepted", diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index c6dff51e0662..6d4909cd0c80 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -24,12 +24,12 @@ import { optionalStringEnum } from "../schema/typebox.js"; import type { SpawnedToolContext } from "../spawned-context.js"; import { resolveAcpSessionsSpawnImageAttachments } from "../subagent-attachments.js"; import { getSubagentDeliveryBacklogPressure } from "../subagent-registry.js"; +import { normalizeSubagentTaskName } from "../subagent-task-name.js"; import { SUBAGENT_SPAWN_CONTEXT_MODES, SUBAGENT_SPAWN_MODES, spawnSubagentDirect, -} from "../subagent-spawn.js"; -import { normalizeSubagentTaskName } from "../subagent-task-name.js"; +} from "../subagents/spawn/subagent-spawn.js"; import { SWARM_CODE_MODE_IDEMPOTENCY_KEY, SWARM_CODE_MODE_REQUEST_FINGERPRINT, @@ -72,10 +72,10 @@ const UNSUPPORTED_SESSIONS_SPAWN_PARAM_KEYS = [ "replyTo", "reply_to", ] as const; -type AcpSpawnModule = typeof import("../acp-spawn.js"); +type AcpSpawnModule = typeof import("../subagents/spawn/acp-spawn.js"); const acpSpawnModuleLoader = createLazyImportLoader( - () => import("../acp-spawn.js"), + () => import("../subagents/spawn/acp-spawn.js"), ); async function loadAcpSpawnModule(): Promise { diff --git a/src/agents/tools/sessions-spawn-visible.ts b/src/agents/tools/sessions-spawn-visible.ts index fd45a4f54689..18ce16df0fd7 100644 --- a/src/agents/tools/sessions-spawn-visible.ts +++ b/src/agents/tools/sessions-spawn-visible.ts @@ -20,9 +20,9 @@ import { resolveSandboxRuntimeStatus } from "../sandbox/runtime-status.js"; import { resolveSpawnedWorkspaceInheritance } from "../spawned-context.js"; import { getSubagentDepthFromSessionStore } from "../subagent-depth.js"; import { countActiveRunsForSession, registerSubagentRun } from "../subagent-registry.js"; -import { resolveSubagentSpawnOwnership } from "../subagent-spawn-ownership.js"; -import { resolveConfiguredSubagentRunTimeoutSeconds } from "../subagent-spawn-plan.js"; import { resolveSubagentTargetPolicy } from "../subagent-target-policy.js"; +import { resolveSubagentSpawnOwnership } from "../subagents/spawn/subagent-spawn-ownership.js"; +import { resolveConfiguredSubagentRunTimeoutSeconds } from "../subagents/spawn/subagent-spawn-plan.js"; import { normalizeToolModelOverride, readStringParam, ToolInputError } from "./common.js"; import { callInProcessGatewayTool, diff --git a/src/agents/tools/swarm-tools.integration.test.ts b/src/agents/tools/swarm-tools.integration.test.ts index 0d31ecd85cab..8666e444c1b0 100644 --- a/src/agents/tools/swarm-tools.integration.test.ts +++ b/src/agents/tools/swarm-tools.integration.test.ts @@ -8,7 +8,7 @@ import { testing as registryTesting, } from "../subagent-registry.test-helpers.js"; import "../subagent-registry.mocks.shared.js"; -import { testing as spawnTesting } from "../subagent-spawn.test-support.js"; +import { testing as spawnTesting } from "../subagents/spawn/subagent-spawn.test-support.js"; import { testing as swarmSchedulerTesting } from "../subagents/swarm/swarm-scheduler.test-support.js"; import { createAgentsWaitTool } from "./agents-wait-tool.js"; import { createSessionsSpawnTool } from "./sessions-spawn-tool.js"; diff --git a/src/auto-reply/reply/commands-acp.test.ts b/src/auto-reply/reply/commands-acp.test.ts index bf921342d629..4fe5d340c12f 100644 --- a/src/auto-reply/reply/commands-acp.test.ts +++ b/src/auto-reply/reply/commands-acp.test.ts @@ -98,7 +98,7 @@ vi.mock("../../acp/runtime/session-meta.js", () => ({ resolveSessionStorePathForAcp: (args: unknown) => hoisted.resolveSessionStorePathForAcpMock(args), })); -vi.mock("../../agents/acp-spawn.js", () => ({ +vi.mock("../../agents/subagents/spawn/acp-spawn.js", () => ({ resolveAcpSpawnRuntimePolicyError: (params: { cfg?: OpenClawConfig }) => params.cfg?.agents?.defaults?.sandbox?.mode === "all" ? 'Sandboxed sessions cannot spawn ACP sessions because runtime="acp" runs on the host. Use runtime="subagent" from sandboxed sessions.' diff --git a/src/auto-reply/reply/commands-acp/lifecycle.ts b/src/auto-reply/reply/commands-acp/lifecycle.ts index 1a28f2ecd804..1a8399861178 100644 --- a/src/auto-reply/reply/commands-acp/lifecycle.ts +++ b/src/auto-reply/reply/commands-acp/lifecycle.ts @@ -15,11 +15,11 @@ import { resolveAcpDispatchPolicyMessage, } from "../../../acp/policy.js"; import { resolveSessionStorePathForAcp } from "../../../acp/runtime/session-meta.js"; +import { resolveSpawnedWorkspaceInheritance } from "../../../agents/spawned-context.js"; import { resolveAcpSpawnRuntimePolicyError, resolveRuntimeCwdForAcpSpawn, -} from "../../../agents/acp-spawn.js"; -import { resolveSpawnedWorkspaceInheritance } from "../../../agents/spawned-context.js"; +} from "../../../agents/subagents/spawn/acp-spawn.js"; import { updateSessionEntry } from "../../../config/sessions/session-accessor.js"; import type { SessionAcpMeta } from "../../../config/sessions/types.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; diff --git a/src/gateway/gateway-acp-spawn-defaults.live.test.ts b/src/gateway/gateway-acp-spawn-defaults.live.test.ts index 8f88d733b3e0..ec613cf0e120 100644 --- a/src/gateway/gateway-acp-spawn-defaults.live.test.ts +++ b/src/gateway/gateway-acp-spawn-defaults.live.test.ts @@ -10,8 +10,8 @@ import { asNullableRecord } from "@openclaw/normalization-core/record-coerce"; import { describe, expect, it } from "vitest"; import { getAcpSessionManager } from "../acp/control-plane/manager.js"; import { getAcpRuntimeBackend } from "../acp/runtime/registry.js"; -import { isSpawnAcpAcceptedResult, spawnAcpDirect } from "../agents/acp-spawn.js"; import { isLiveTestEnabled, readLiveTestConfig } from "../agents/live-test-helpers.js"; +import { isSpawnAcpAcceptedResult, spawnAcpDirect } from "../agents/subagents/spawn/acp-spawn.js"; import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js"; import { resolveStorePath } from "../config/sessions/paths.js"; import { loadSessionEntry } from "../config/sessions/session-accessor.js"; diff --git a/src/gateway/gateway-codex-harness.live.test.ts b/src/gateway/gateway-codex-harness.live.test.ts index adf774462a95..5982a0777456 100644 --- a/src/gateway/gateway-codex-harness.live.test.ts +++ b/src/gateway/gateway-codex-harness.live.test.ts @@ -1857,7 +1857,7 @@ async function verifyCodexSubagentProbe(params: { }); try { const { testing: subagentSpawnTesting, spawnSubagentDirect } = - await import("../agents/subagent-spawn.test-support.js"); + await import("../agents/subagents/spawn/subagent-spawn.test-support.js"); const noOpContextEngine: ContextEngine = { info: { id: "codex-harness-subagent-smoke", name: "Codex harness subagent smoke" }, ingest: async () => ({ ingested: false }), @@ -1980,7 +1980,7 @@ async function verifyCodexSubagentProbe(params: { }); } finally { const { testing: subagentSpawnTesting } = - await import("../agents/subagent-spawn.test-support.js"); + await import("../agents/subagents/spawn/subagent-spawn.test-support.js"); subagentSpawnTesting.setDepsForTest(); unsubscribe(); } diff --git a/src/gateway/server-methods/agent-task-tracking.ts b/src/gateway/server-methods/agent-task-tracking.ts index 363b9be2a639..bd8c3d557034 100644 --- a/src/gateway/server-methods/agent-task-tracking.ts +++ b/src/gateway/server-methods/agent-task-tracking.ts @@ -101,7 +101,7 @@ export function resolveGatewayAgentTaskTrackingMode(params: { return "plugin_subagent"; } // A confirmed ACP manual-spawn child turn already owns its requester-visible - // `acp` task row from the spawn control plane (src/agents/acp-spawn.ts). The + // `acp` task row from the spawn control plane (src/agents/subagents/spawn/acp-spawn.ts). The // Gateway CLI path runs that same childRunId, so tracking it here would emit a // duplicate row for one run. Suppress only the CLI branch; plugin-subagent and // normal CLI tracking stay intact. diff --git a/src/tasks/task-registry.test.ts b/src/tasks/task-registry.test.ts index 9c267c81d9d0..952721efef16 100644 --- a/src/tasks/task-registry.test.ts +++ b/src/tasks/task-registry.test.ts @@ -1,8 +1,8 @@ // Covers task registry lifecycle, delivery, notification, and query behavior. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { AcpSessionStoreEntry } from "../acp/runtime/session-meta.js"; -import { startAcpSpawnParentStreamRelay } from "../agents/acp-spawn-parent-stream.js"; import { emitAcpLifecycleStart } from "../agents/command/attempt-execution.js"; +import { startAcpSpawnParentStreamRelay } from "../agents/subagents/spawn/acp-spawn-parent-stream.js"; import { resetCronActiveJobs } from "../cron/active-jobs.js"; import { emitAgentEvent, resetAgentEventsForTest } from "../infra/agent-events.js"; import { registerAgentRunContext } from "../infra/agent-run-registry.js"; diff --git a/test/scripts/audit-seams.test.ts b/test/scripts/audit-seams.test.ts index 83f4c82e14f6..adf6c1c9e721 100644 --- a/test/scripts/audit-seams.test.ts +++ b/test/scripts/audit-seams.test.ts @@ -54,9 +54,9 @@ describe("audit-seams cron seam classification", () => { describe("audit-seams subagent seam classification", () => { it("detects subagent spawn and cleanup handoff boundaries", () => { const source = ` - import { callGateway } from "../gateway/call.js"; - import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; - import { registerSubagentRun } from "./subagent-registry.js"; + import { callGateway } from "../../../gateway/call.js"; + import { emitSessionLifecycleEvent } from "../../../sessions/session-lifecycle-events.js"; + import { registerSubagentRun } from "../../subagent-registry.js"; export async function spawnSubagentDirect() { const response = await callGateway({ method: "agent.run", params: { task: "do it" } }); @@ -67,7 +67,7 @@ describe("audit-seams subagent seam classification", () => { } `; - expect(describeSeamKinds("src/agents/subagent-spawn.ts", source)).toEqual([ + expect(describeSeamKinds("src/agents/subagents/spawn/subagent-spawn.ts", source)).toEqual([ "subagent-lifecycle-registry", "subagent-session-cleanup", "subagent-session-spawn", @@ -112,9 +112,9 @@ describe("audit-seams subagent seam classification", () => { it("detects parent-stream seams for ACP spawn relays", () => { const source = ` - import { onAgentEvent } from "../infra/agent-events.js"; - import { requestHeartbeat } from "../infra/heartbeat-wake.js"; - import { enqueueSystemEvent } from "../infra/system-events.js"; + import { onAgentEvent } from "../../../infra/agent-events.js"; + import { requestHeartbeat } from "../../../infra/heartbeat-wake.js"; + import { enqueueSystemEvent } from "../../../infra/system-events.js"; export function startAcpSpawnParentStreamRelay() { onAgentEvent("agent-output", () => {}); @@ -129,9 +129,9 @@ describe("audit-seams subagent seam classification", () => { } `; - expect(describeSeamKinds("src/agents/acp-spawn-parent-stream.ts", source)).toEqual([ - "subagent-parent-stream", - ]); + expect( + describeSeamKinds("src/agents/subagents/spawn/acp-spawn-parent-stream.ts", source), + ).toEqual(["subagent-parent-stream"]); }); }); @@ -153,7 +153,12 @@ describe("audit-seams status/help", () => { expect( determineSeamTestStatus( ["subagent-session-spawn"], - [{ file: "src/agents/subagent-spawn.workspace.test.ts", matchQuality: "direct-import" }], + [ + { + file: "src/agents/subagents/spawn/subagent-spawn.workspace.test.ts", + matchQuality: "direct-import", + }, + ], ), ).toEqual({ status: "partial", diff --git a/test/scripts/check-session-accessor-boundary.test.ts b/test/scripts/check-session-accessor-boundary.test.ts index f2d6395e2179..ab6da9eebe57 100644 --- a/test/scripts/check-session-accessor-boundary.test.ts +++ b/test/scripts/check-session-accessor-boundary.test.ts @@ -58,7 +58,7 @@ describe("session accessor boundary guard", () => { "src/acp/control-plane/manager.background-task.ts", "src/acp/control-plane/manager.core.ts", "src/acp/runtime/session-meta.ts", - "src/agents/acp-spawn.ts", + "src/agents/subagents/spawn/acp-spawn.ts", "src/agents/auth-profiles/session-override.ts", "src/agents/embedded-agent-runner/compaction-successor-transcript.ts", "src/agents/embedded-agent-runner/run/attempt.ts",