mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:40:43 +00:00
fix: retire one-shot subagent MCP runtimes
This commit is contained in:
@@ -79,8 +79,8 @@ async function disposeSession(session: BundleMcpSession) {
|
|||||||
if (session.transportType === "streamable-http") {
|
if (session.transportType === "streamable-http") {
|
||||||
await (session.transport as StreamableHTTPClientTransport).terminateSession().catch(() => {});
|
await (session.transport as StreamableHTTPClientTransport).terminateSession().catch(() => {});
|
||||||
}
|
}
|
||||||
await session.client.close().catch(() => {});
|
|
||||||
await session.transport.close().catch(() => {});
|
await session.transport.close().catch(() => {});
|
||||||
|
await session.client.close().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCatalogFingerprint(servers: Record<string, unknown>): string {
|
function createCatalogFingerprint(servers: Record<string, unknown>): string {
|
||||||
@@ -454,6 +454,23 @@ export async function retireSessionMcpRuntime(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function retireSessionMcpRuntimeForSessionKey(params: {
|
||||||
|
sessionKey?: string | null;
|
||||||
|
reason: string;
|
||||||
|
onError?: (error: unknown, sessionId: string, reason: string) => void;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const sessionKey = normalizeOptionalString(params.sessionKey);
|
||||||
|
if (!sessionKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sessionId = getSessionMcpRuntimeManager().resolveSessionId(sessionKey);
|
||||||
|
return await retireSessionMcpRuntime({
|
||||||
|
sessionId,
|
||||||
|
reason: params.reason,
|
||||||
|
onError: params.onError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function disposeAllSessionMcpRuntimes(): Promise<void> {
|
export async function disposeAllSessionMcpRuntimes(): Promise<void> {
|
||||||
await getSessionMcpRuntimeManager().disposeAll();
|
await getSessionMcpRuntimeManager().disposeAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export {
|
|||||||
getOrCreateSessionMcpRuntime,
|
getOrCreateSessionMcpRuntime,
|
||||||
getSessionMcpRuntimeManager,
|
getSessionMcpRuntimeManager,
|
||||||
retireSessionMcpRuntime,
|
retireSessionMcpRuntime,
|
||||||
|
retireSessionMcpRuntimeForSessionKey,
|
||||||
} from "./pi-bundle-mcp-runtime.js";
|
} from "./pi-bundle-mcp-runtime.js";
|
||||||
export {
|
export {
|
||||||
createBundleMcpToolRuntime,
|
createBundleMcpToolRuntime,
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ import {
|
|||||||
} from "../model-auth.js";
|
} from "../model-auth.js";
|
||||||
import { normalizeProviderId } from "../model-selection.js";
|
import { normalizeProviderId } from "../model-selection.js";
|
||||||
import { ensureOpenClawModelsJson } from "../models-config.js";
|
import { ensureOpenClawModelsJson } from "../models-config.js";
|
||||||
import { retireSessionMcpRuntime } from "../pi-bundle-mcp-tools.js";
|
import {
|
||||||
|
retireSessionMcpRuntime,
|
||||||
|
retireSessionMcpRuntimeForSessionKey,
|
||||||
|
} from "../pi-bundle-mcp-tools.js";
|
||||||
import {
|
import {
|
||||||
classifyFailoverReason,
|
classifyFailoverReason,
|
||||||
extractObservedOverflowTokenCount,
|
extractObservedOverflowTokenCount,
|
||||||
@@ -2131,15 +2134,23 @@ export async function runEmbeddedPiAgent(
|
|||||||
await contextEngine.dispose?.();
|
await contextEngine.dispose?.();
|
||||||
stopRuntimeAuthRefreshTimer();
|
stopRuntimeAuthRefreshTimer();
|
||||||
if (params.cleanupBundleMcpOnRunEnd === true) {
|
if (params.cleanupBundleMcpOnRunEnd === true) {
|
||||||
await retireSessionMcpRuntime({
|
const onError = (error: unknown, sessionId: string) => {
|
||||||
sessionId: params.sessionId,
|
log.warn(
|
||||||
|
`bundle-mcp cleanup failed after run for ${sessionId}: ${formatErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const retiredBySessionKey = await retireSessionMcpRuntimeForSessionKey({
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
reason: "embedded-run-end",
|
reason: "embedded-run-end",
|
||||||
onError: (error, sessionId) => {
|
onError,
|
||||||
log.warn(
|
|
||||||
`bundle-mcp cleanup failed after run for ${sessionId}: ${formatErrorMessage(error)}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
if (!retiredBySessionKey) {
|
||||||
|
await retireSessionMcpRuntime({
|
||||||
|
sessionId: params.sessionId,
|
||||||
|
reason: "embedded-run-end",
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
setDetachedTaskDeliveryStatusByRunId,
|
setDetachedTaskDeliveryStatusByRunId,
|
||||||
} from "../tasks/detached-task-runtime.js";
|
} from "../tasks/detached-task-runtime.js";
|
||||||
import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js";
|
import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js";
|
||||||
|
import { retireSessionMcpRuntimeForSessionKey } from "./pi-bundle-mcp-tools.js";
|
||||||
import { type SubagentRunOutcome, withSubagentOutcomeTiming } from "./subagent-announce-output.js";
|
import { type SubagentRunOutcome, withSubagentOutcomeTiming } from "./subagent-announce-output.js";
|
||||||
import {
|
import {
|
||||||
SUBAGENT_ENDED_REASON_COMPLETE,
|
SUBAGENT_ENDED_REASON_COMPLETE,
|
||||||
@@ -383,6 +384,20 @@ export function createSubagentRegistryLifecycleController(params: {
|
|||||||
cleanup: "delete" | "keep";
|
cleanup: "delete" | "keep";
|
||||||
completedAt: number;
|
completedAt: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
if (cleanupParams.entry.spawnMode !== "session") {
|
||||||
|
void retireSessionMcpRuntimeForSessionKey({
|
||||||
|
sessionKey: cleanupParams.entry.childSessionKey,
|
||||||
|
reason: "subagent-run-cleanup",
|
||||||
|
onError: (error, sessionId) => {
|
||||||
|
params.warn("failed to retire subagent bundle MCP runtime", {
|
||||||
|
error: buildSafeLifecycleErrorMeta(error),
|
||||||
|
sessionId,
|
||||||
|
runId: maskRunId(cleanupParams.runId),
|
||||||
|
childSessionKey: maskSessionKey(cleanupParams.entry.childSessionKey),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
if (cleanupParams.cleanup === "delete") {
|
if (cleanupParams.cleanup === "delete") {
|
||||||
params.clearPendingLifecycleError(cleanupParams.runId);
|
params.clearPendingLifecycleError(cleanupParams.runId);
|
||||||
void params.notifyContextEngineSubagentEnded({
|
void params.notifyContextEngineSubagentEnded({
|
||||||
@@ -405,6 +420,28 @@ export function createSubagentRegistryLifecycleController(params: {
|
|||||||
retryDeferredCompletedAnnounces(cleanupParams.runId);
|
retryDeferredCompletedAnnounces(cleanupParams.runId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const retireRunModeBundleMcpRuntime = (cleanupParams: {
|
||||||
|
runId: string;
|
||||||
|
entry: SubagentRunRecord;
|
||||||
|
reason: string;
|
||||||
|
}) => {
|
||||||
|
if (cleanupParams.entry.spawnMode === "session") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void retireSessionMcpRuntimeForSessionKey({
|
||||||
|
sessionKey: cleanupParams.entry.childSessionKey,
|
||||||
|
reason: cleanupParams.reason,
|
||||||
|
onError: (error, sessionId) => {
|
||||||
|
params.warn("failed to retire subagent bundle MCP runtime", {
|
||||||
|
error: buildSafeLifecycleErrorMeta(error),
|
||||||
|
sessionId,
|
||||||
|
runId: maskRunId(cleanupParams.runId),
|
||||||
|
childSessionKey: maskSessionKey(cleanupParams.entry.childSessionKey),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const finalizeSubagentCleanup = async (
|
const finalizeSubagentCleanup = async (
|
||||||
runId: string,
|
runId: string,
|
||||||
cleanup: "delete" | "keep",
|
cleanup: "delete" | "keep",
|
||||||
@@ -689,6 +726,12 @@ export function createSubagentRegistryLifecycleController(params: {
|
|||||||
onWarn: (msg) => params.warn(msg, { runId: entry.runId }),
|
onWarn: (msg) => params.warn(msg, { runId: entry.runId }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
retireRunModeBundleMcpRuntime({
|
||||||
|
runId: completeParams.runId,
|
||||||
|
entry,
|
||||||
|
reason: "subagent-run-complete",
|
||||||
|
});
|
||||||
|
|
||||||
startSubagentAnnounceCleanupFlow(completeParams.runId, entry);
|
startSubagentAnnounceCleanupFlow(completeParams.runId, entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -750,6 +750,7 @@ export async function spawnSubagentDirect(
|
|||||||
idempotencyKey: childIdem,
|
idempotencyKey: childIdem,
|
||||||
deliver: deliverInitialChildRunDirectly,
|
deliver: deliverInitialChildRunDirectly,
|
||||||
lane: AGENT_LANE_SUBAGENT,
|
lane: AGENT_LANE_SUBAGENT,
|
||||||
|
cleanupBundleMcpOnRunEnd: spawnMode !== "session",
|
||||||
extraSystemPrompt: childSystemPrompt,
|
extraSystemPrompt: childSystemPrompt,
|
||||||
thinking: thinkingOverride,
|
thinking: thinkingOverride,
|
||||||
timeout: runTimeoutSeconds,
|
timeout: runTimeoutSeconds,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|||||||
import { callGateway } from "../../gateway/call.js";
|
import { callGateway } from "../../gateway/call.js";
|
||||||
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
|
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
|
||||||
import { resolveNestedAgentLaneForSession } from "../lanes.js";
|
import { resolveNestedAgentLaneForSession } from "../lanes.js";
|
||||||
|
import { retireSessionMcpRuntimeForSessionKey } from "../pi-bundle-mcp-tools.js";
|
||||||
import { waitForAgentRunAndReadUpdatedAssistantReply } from "../run-wait.js";
|
import { waitForAgentRunAndReadUpdatedAssistantReply } from "../run-wait.js";
|
||||||
|
|
||||||
export { readLatestAssistantReply } from "../run-wait.js";
|
export { readLatestAssistantReply } from "../run-wait.js";
|
||||||
@@ -55,6 +56,12 @@ export async function runAgentStep(params: {
|
|||||||
sessionKey: params.sessionKey,
|
sessionKey: params.sessionKey,
|
||||||
timeoutMs: Math.min(params.timeoutMs, 60_000),
|
timeoutMs: Math.min(params.timeoutMs, 60_000),
|
||||||
});
|
});
|
||||||
|
if (result.status === "ok" || result.status === "error") {
|
||||||
|
await retireSessionMcpRuntimeForSessionKey({
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
reason: "nested-agent-step-complete",
|
||||||
|
});
|
||||||
|
}
|
||||||
if (result.status !== "ok") {
|
if (result.status !== "ok") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export const AgentParamsSchema = Type.Object(
|
|||||||
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||||
bestEffortDeliver: Type.Optional(Type.Boolean()),
|
bestEffortDeliver: Type.Optional(Type.Boolean()),
|
||||||
lane: Type.Optional(Type.String()),
|
lane: Type.Optional(Type.String()),
|
||||||
|
cleanupBundleMcpOnRunEnd: Type.Optional(Type.Boolean()),
|
||||||
extraSystemPrompt: Type.Optional(Type.String()),
|
extraSystemPrompt: Type.Optional(Type.String()),
|
||||||
bootstrapContextMode: Type.Optional(
|
bootstrapContextMode: Type.Optional(
|
||||||
Type.Union([Type.Literal("full"), Type.Literal("lightweight")]),
|
Type.Union([Type.Literal("full"), Type.Literal("lightweight")]),
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
idempotencyKey: string;
|
idempotencyKey: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
bestEffortDeliver?: boolean;
|
bestEffortDeliver?: boolean;
|
||||||
|
cleanupBundleMcpOnRunEnd?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
inputProvenance?: InputProvenance;
|
inputProvenance?: InputProvenance;
|
||||||
};
|
};
|
||||||
@@ -946,6 +947,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
messageChannel: originMessageChannel,
|
messageChannel: originMessageChannel,
|
||||||
runId,
|
runId,
|
||||||
lane: request.lane,
|
lane: request.lane,
|
||||||
|
cleanupBundleMcpOnRunEnd: request.cleanupBundleMcpOnRunEnd === true,
|
||||||
extraSystemPrompt: request.extraSystemPrompt,
|
extraSystemPrompt: request.extraSystemPrompt,
|
||||||
bootstrapContextMode: request.bootstrapContextMode,
|
bootstrapContextMode: request.bootstrapContextMode,
|
||||||
bootstrapContextRunKind: request.bootstrapContextRunKind,
|
bootstrapContextRunKind: request.bootstrapContextRunKind,
|
||||||
|
|||||||
Reference in New Issue
Block a user