openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
185 lines (184 loc) • 7.87 kB
JavaScript
import { t as createAbortError } from "./abort-signal-D2k14JsD.js";
import { D as tryResolveLegacyCompatibilityAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import "./legacy.default-agent-owner-BGwEdQRe.js";
import { y as describeAgentsWaitTool } from "./tool-description-presets-1n9mie5B.js";
import { n as resolvePersistedSessionStoreOwnerForKey } from "./session-store-owner-CR2Ag3xK.js";
import { n as ToolInputError } from "./tool-input-error-mjW74R8m.js";
import { t as jsonResult } from "./tool-results-BCM3fdVS.js";
import "./common-Bm6UTDDA.js";
import { a as markCollectorReaderTool } from "./swarm-collector-capability-CNerMvSC.js";
import { x as onSubagentRegistryPersisted } from "./subagent-registry-read-DeFf_9Nz.js";
import { t as resolveSubagentCompletionResultText } from "./subagent-completion-result-D8RxJ29O.js";
import { u as getSubagentRunsByRunIds } from "./subagent-registry-BMJDw92y.js";
import { t as resolveSwarmConfig } from "./swarm-config-XTe8M2h4.js";
import { Type } from "typebox";
//#region src/agents/tools/agents-wait-tool.ts
const MAX_WAIT_IDS = 1e3;
const AgentsWaitToolSchema = Type.Object({
ids: Type.Array(Type.String({ minLength: 1 }), {
minItems: 1,
maxItems: MAX_WAIT_IDS
}),
timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 }))
});
function ownsRun(entry, currentSessionKeys, currentAgentId, config) {
const owner = entry.swarmRequesterSessionKey?.trim();
if (!owner) return false;
return (entry.swarmWaitOwnerSessionKeys && entry.swarmWaitOwnerSessionKeys.length > 0 ? entry.swarmWaitOwnerSessionKeys : [owner]).some((sessionKey) => {
if (!currentSessionKeys.has(sessionKey)) return false;
const ownerAgentId = parseAgentSessionKey(sessionKey)?.agentId ?? entry.requesterAgentId ?? paramsOwner(config, sessionKey);
return Boolean(ownerAgentId && (!currentAgentId || ownerAgentId === currentAgentId));
});
}
function paramsOwner(config, sessionKey) {
if (!config) return;
const persisted = resolvePersistedSessionStoreOwnerForKey(config, sessionKey);
return persisted.kind === "configured" ? persisted.agentId : persisted.kind === "none" ? tryResolveLegacyCompatibilityAgentId(config) : void 0;
}
function completionResult(entry) {
const completion = entry.collectorCompletion;
if (!completion) return;
return {
runId: entry.swarmRunId ?? entry.runId,
status: completion.status,
result: resolveSubagentCompletionResultText(entry) ?? "",
...completion.structured !== void 0 ? { structured: completion.structured } : {},
...entry.execution.outcome?.status === "error" ? { error: entry.execution.outcome.error } : {},
...completion.schemaError ? { schemaError: completion.schemaError } : {},
sessionKey: entry.childSessionKey,
...entry.label ? { label: entry.label } : {},
...completion.usage ? { usage: completion.usage } : {}
};
}
/** Park one host bridge until its collector completes; registry writes wake it without polling. */
async function waitForCollectorCompletion(params) {
const readCompletion = () => {
const state = readWaitState([params.runId], params.currentSessionKeys, params.currentAgentId, params.config);
const error = state.errors?.[0];
if (error) throw new ToolInputError(`agents.run ${error.error}: ${error.runId}`);
return state.completed[0];
};
const immediate = readCompletion();
if (immediate) return immediate;
if (params.signal?.aborted) throw new ToolInputError("agents.run wait aborted.");
return await new Promise((resolve, reject) => {
let settled = false;
const finish = (result) => {
if (settled) return;
settled = true;
unsubscribe();
params.signal?.removeEventListener("abort", onAbort);
if (result instanceof Error) reject(result);
else resolve(result);
};
const check = () => {
try {
const completion = readCompletion();
if (completion) finish(completion);
} catch (error) {
finish(error instanceof Error ? error : new Error(String(error)));
}
};
const onAbort = () => finish(new ToolInputError("agents.run wait aborted."));
const unsubscribe = onSubagentRegistryPersisted(check);
params.signal?.addEventListener("abort", onAbort, { once: true });
if (params.signal?.aborted) onAbort();
else check();
});
}
function readWaitState(ids, currentSessionKeys, currentAgentId, config) {
const errors = [];
const completed = [];
const pending = [];
const snapshot = getSubagentRunsByRunIds(ids);
for (const [inputIndex, runId] of ids.entries()) {
const entry = snapshot.entries.get(runId);
if (!entry?.collect) {
errors.push({
runId,
error: "not_found"
});
continue;
}
if (!ownsRun(entry, currentSessionKeys, currentAgentId, config)) {
errors.push({
runId,
error: "not_owner"
});
continue;
}
const result = completionResult(entry);
if (result) completed.push({
result,
completedAt: entry.completion?.capturedAt ?? entry.execution.endedAt ?? Number.MAX_SAFE_INTEGER,
inputIndex
});
else pending.push(runId);
}
completed.sort((left, right) => left.completedAt - right.completedAt || left.inputIndex - right.inputIndex);
return {
completed: completed.map((entry) => entry.result),
pending,
...errors.length > 0 ? { errors } : {}
};
}
async function waitForCollector(params) {
const deadline = performance.now() + params.timeoutMs;
for (;;) {
if (params.signal?.aborted) throw createAbortError("agents_wait aborted.");
const state = readWaitState(params.ids, params.currentSessionKeys, params.currentAgentId, params.config);
if (state.completed.length > 0 || state.pending.length === 0 || performance.now() >= deadline) return state;
await new Promise((resolve, reject) => {
const finish = (error) => {
clearTimeout(timer);
unsubscribe();
params.signal?.removeEventListener("abort", onAbort);
if (error) {
reject(error);
return;
}
resolve();
};
const onAbort = () => finish(createAbortError("agents_wait aborted."));
const timer = setTimeout(finish, Math.min(500, Math.max(0, deadline - performance.now())));
const unsubscribe = onSubagentRegistryPersisted(() => finish());
params.signal?.addEventListener("abort", onAbort, { once: true });
if (params.signal?.aborted) onAbort();
});
}
}
function createAgentsWaitTool(opts) {
const swarm = resolveSwarmConfig(opts.config, opts.agentId);
return markCollectorReaderTool({
label: "Wait for Agents",
name: "agents_wait",
displaySummary: "Wait for collector children.",
description: describeAgentsWaitTool(false),
parameters: AgentsWaitToolSchema,
execute: async (_toolCallId, args, signal) => {
const params = args;
if (params.ids.length > MAX_WAIT_IDS) throw new ToolInputError(`agents_wait supports at most ${MAX_WAIT_IDS} ids.`);
const ids = [...new Set(params.ids.map((id) => id.trim()).filter(Boolean))];
if (ids.length === 0) throw new ToolInputError("agents_wait requires at least one non-empty run id.");
const currentSessionKeys = new Set([opts.runSessionKey, opts.agentSessionKey].filter((key) => Boolean(key?.trim())));
const requestedTimeout = typeof params.timeoutSeconds === "number" && Number.isFinite(params.timeoutSeconds) ? params.timeoutSeconds : 30;
const timeoutSeconds = Math.min(Math.max(0, requestedTimeout), swarm.waitTimeoutSecondsMax);
const result = await waitForCollector({
ids,
currentSessionKeys,
currentAgentId: opts.agentId,
config: opts.config,
timeoutMs: timeoutSeconds * 1e3,
signal
});
const noAuthorizedTargets = result.completed.length === 0 && result.pending.length === 0 && Boolean(result.errors?.length);
return jsonResult(noAuthorizedTargets ? {
...result,
success: false
} : result);
}
});
}
//#endregion
export { waitForCollectorCompletion as n, createAgentsWaitTool as t };