refactor(agents): fold single-consumer subagent-spawn helpers back into callers (#114301)

This commit is contained in:
Peter Steinberger
2026-07-27 01:17:31 -04:00
committed by GitHub
parent e0eed257c8
commit beef065fe4
5 changed files with 85 additions and 94 deletions
+55 -4
View File
@@ -3,6 +3,10 @@ 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 {
@@ -15,21 +19,68 @@ import type {
SpawnSubagentParams,
SpawnSubagentResult,
} from "./subagent-spawn-contract.js";
import {
buildResolvedSubagentModelMetadata,
resolveCollectorOutputModelError,
} from "./subagent-spawn-model.js";
import { getSubagentSpawnDeps } from "./subagent-spawn-deps.js";
import { resolveSubagentModelAndThinkingPlan, splitModelRef } from "./subagent-spawn-plan.js";
import {
readRequesterFastMode,
readRequesterThinkingLevel,
} from "./subagent-spawn-requester-prefs.js";
import {
loadPreparedModelCatalog,
normalizeDeliveryContext,
resolveAgentConfig,
resolveSandboxRuntimeStatus,
} from "./subagent-spawn.runtime.js";
function buildResolvedSubagentModelMetadata(resolvedModel?: string): {
resolvedModel?: string;
resolvedProvider?: string;
} {
const modelRef = resolvedModel?.trim();
if (!modelRef) {
return {};
}
const { provider } = splitModelRef(modelRef);
return {
resolvedModel: modelRef,
...(provider ? { resolvedProvider: provider } : {}),
};
}
async function resolveCollectorOutputModelError(params: {
cfg: OpenClawConfig;
targetAgentId: string;
targetAgentDir: string;
workspaceDir?: string;
resolvedModel?: string;
}): Promise<string | undefined> {
const selected = splitModelRef(params.resolvedModel);
const fallback = resolveDefaultModelForAgent({
cfg: params.cfg,
agentId: params.targetAgentId,
});
const provider = selected.provider ?? fallback.provider;
const model = selected.model ?? fallback.model;
if (!provider || !model) {
return undefined;
}
let catalog: Awaited<ReturnType<typeof loadPreparedModelCatalog>>;
try {
catalog = await getSubagentSpawnDeps().loadPreparedModelCatalog({
config: params.cfg,
agentDir: params.targetAgentDir,
workspaceDir: params.workspaceDir,
});
} catch (error) {
return `sessions_spawn could not verify outputSchema model capabilities: ${summarizeSpawnError(error)}`;
}
const entry = findModelCatalogEntry(catalog, { provider, modelId: model });
if (!entry || supportsModelTools(entry)) {
return undefined;
}
return `sessions_spawn outputSchema requires a tool-capable target model; "${provider}/${model}" declares compat.supportsTools=false.`;
}
type ResolvedSubagentChildPlan = {
spawnedCwd?: string;
toolSpawnMetadata: ReturnType<typeof mapToolContextToSpawnedRunMetadata>;
-57
View File
@@ -1,57 +0,0 @@
import type { OpenClawConfig } from "../config/types.openclaw.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 { getSubagentSpawnDeps } from "./subagent-spawn-deps.js";
import { splitModelRef } from "./subagent-spawn-plan.js";
import { loadPreparedModelCatalog } from "./subagent-spawn.runtime.js";
export function buildResolvedSubagentModelMetadata(resolvedModel?: string): {
resolvedModel?: string;
resolvedProvider?: string;
} {
const modelRef = resolvedModel?.trim();
if (!modelRef) {
return {};
}
const { provider } = splitModelRef(modelRef);
return {
resolvedModel: modelRef,
...(provider ? { resolvedProvider: provider } : {}),
};
}
export async function resolveCollectorOutputModelError(params: {
cfg: OpenClawConfig;
targetAgentId: string;
targetAgentDir: string;
workspaceDir?: string;
resolvedModel?: string;
}): Promise<string | undefined> {
const selected = splitModelRef(params.resolvedModel);
const fallback = resolveDefaultModelForAgent({
cfg: params.cfg,
agentId: params.targetAgentId,
});
const provider = selected.provider ?? fallback.provider;
const model = selected.model ?? fallback.model;
if (!provider || !model) {
return undefined;
}
let catalog: Awaited<ReturnType<typeof loadPreparedModelCatalog>>;
try {
catalog = await getSubagentSpawnDeps().loadPreparedModelCatalog({
config: params.cfg,
agentDir: params.targetAgentDir,
workspaceDir: params.workspaceDir,
});
} catch (error) {
return `sessions_spawn could not verify outputSchema model capabilities: ${summarizeSpawnError(error)}`;
}
const entry = findModelCatalogEntry(catalog, { provider, modelId: model });
if (!entry || supportsModelTools(entry)) {
return undefined;
}
return `sessions_spawn outputSchema requires a tool-capable target model; "${provider}/${model}" declares compat.supportsTools=false.`;
}
+5 -1
View File
@@ -3,6 +3,7 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coe
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 { resolveSpawnAdmission, resolveSpawnMode } from "./spawn-plan.js";
import { listSwarmRunsForGroup } from "./subagent-registry.js";
import { resolveSubagentContextMode } from "./subagent-spawn-context.js";
@@ -15,13 +16,16 @@ import { getSubagentSpawnDeps } from "./subagent-spawn-deps.js";
import { resolveSubagentSpawnOwnership } from "./subagent-spawn-ownership.js";
import { resolveConfiguredSubagentRunTimeoutSeconds } from "./subagent-spawn-plan.js";
import { loadSubagentConfig } from "./subagent-spawn-session-patch.js";
import { resolveConfiguredAgentIds } from "./subagent-spawn-validation.js";
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./subagent-spawn.runtime.js";
import { normalizeSubagentTaskName } from "./subagent-task-name.js";
import { resolveSwarmConfig } from "./swarm-config.js";
import { validateStructuredOutputSchema } from "./swarm-output-schema.js";
import { reserveSwarmRun } from "./swarm-scheduler.js";
function resolveConfiguredAgentIds(cfg: OpenClawConfig): string[] {
return listAgentIds(cfg);
}
type ResolvedSubagentSpawnRequest = {
request: {
taskName?: string;
-31
View File
@@ -1,31 +0,0 @@
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { listAgentIds } from "./agent-scope-config.js";
export function resolveConfiguredAgentIds(cfg: OpenClawConfig): string[] {
return listAgentIds(cfg);
}
export function sanitizeMountPathHint(value?: string): string | undefined {
const trimmed = normalizeOptionalString(value);
if (!trimmed) {
return undefined;
}
if (hasPromptUnsafeControlCharacter(trimmed)) {
return undefined;
}
if (!/^[A-Za-z0-9._\-/:]+$/.test(trimmed)) {
return undefined;
}
return trimmed;
}
function hasPromptUnsafeControlCharacter(value: string): boolean {
for (const char of value) {
const code = char.charCodeAt(0);
if (code <= 0x1f || code === 0x7f || code === 0x85 || code === 0x2028 || code === 0x2029) {
return true;
}
}
return false;
}
+25 -1
View File
@@ -4,6 +4,7 @@
* Validates spawn requests, prepares child sessions, stages attachments, binds delivery context, and registers runs.
*/
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 { isFastTestRuntimeEnv } from "../infra/env.js";
@@ -57,7 +58,6 @@ import {
bindThreadForSubagentSpawn,
hasRoutableDeliveryOrigin,
} from "./subagent-spawn-thread-binding.js";
import { sanitizeMountPathHint } from "./subagent-spawn-validation.js";
import {
buildSubagentSystemPrompt,
emitSessionLifecycleEvent,
@@ -69,6 +69,30 @@ export { SUBAGENT_SPAWN_CONTEXT_MODES, SUBAGENT_SPAWN_MODES } from "./subagent-s
const SUBAGENT_CONTROL_GATEWAY_TIMEOUT_MS = 60_000;
function sanitizeMountPathHint(value?: string): string | undefined {
const trimmed = normalizeOptionalString(value);
if (!trimmed) {
return undefined;
}
if (hasPromptUnsafeControlCharacter(trimmed)) {
return undefined;
}
if (!/^[A-Za-z0-9._\-/:]+$/.test(trimmed)) {
return undefined;
}
return trimmed;
}
function hasPromptUnsafeControlCharacter(value: string): boolean {
for (const char of value) {
const code = char.charCodeAt(0);
if (code <= 0x1f || code === 0x7f || code === 0x85 || code === 0x2028 || code === 0x2029) {
return true;
}
}
return false;
}
export async function spawnSubagentDirect(
params: SpawnSubagentParams,
ctx: SpawnSubagentContext,