openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
638 lines (637 loc) • 27.3 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { t as sleep } from "./sleep-D7nua6TP.js";
import { t as getActivePluginGatewayNodePolicyRegistry } from "./runtime-state-Ccxn1fO9.js";
import { a as getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope-BCMYlsDI.js";
import { n as recordRuntimeActionDecision } from "./runtime-action-decision-C4JNkXkP.js";
import { i as sanitizeExecApprovalWarningText, n as sanitizeExecApprovalDisplayText } from "./exec-approval-text-sanitize-C1BzZQH8.js";
import { t as sanitizeApprovalScope } from "./approval-scope-BL54HpUv.js";
import { f as resolvePluginApprovalTimeoutMs } from "./plugin-approvals-BUrr4V_c.js";
import { t as resolveCanonicalPluginApprovalRequestAllowedDecisions } from "./plugin-approval-canonical-decisions-CwBFXTtw.js";
import { l as resolveNodeCommandAllowlist, o as isNodeCommandAllowed } from "./node-command-policy-H4FpS0aA.js";
import "./node-registry.invoke-stream-BOFc1_TA.js";
import { i as buildRequestedApprovalEvent, s as handlePendingApprovalRequest, t as bindApprovalRequesterMetadata } from "./approval-shared-ahH5xF_7.js";
import { t as ApprovalObserverClosedError } from "./exec-approval-lifecycle-CRyRxBLA.js";
import { t as runApprovalRequestDeliveries } from "./approval-request-delivery-BVUQ3faZ.js";
import { randomUUID } from "node:crypto";
//#region src/gateway/node-invoke-placement-grant.ts
function isBindingCurrentForOwner(owner, binding) {
try {
owner.assertCurrent(binding);
return true;
} catch {
return false;
}
}
function resolveNodeInvokePlacementGrant(params) {
const binding = params.requestedDecisions?.includes("allow-always") === true && params.owner && params.approvalScope !== void 0 && params.risk?.level === "high" && params.nodeSession.pairingGeneration && params.runtime ? params.runtime.resolveBinding({
pluginId: params.pluginId,
command: params.command,
approvalScope: params.approvalScope,
agentId: params.owner.agentId,
sessionKey: params.owner.sessionKey,
nodeId: params.nodeSession.nodeId,
pairingGeneration: params.nodeSession.pairingGeneration
}) : null;
const currentBinding = binding && params.owner && isBindingCurrentForOwner(params.owner, binding) ? binding : null;
if (currentBinding && params.runtime) {
const existing = params.runtime.validate(currentBinding);
if (existing.outcome === "consumed") return {
kind: "granted",
binding: currentBinding,
approvalId: existing.grant.mintedByApprovalId
};
}
return {
kind: "prompt",
binding: currentBinding,
allowedDecisions: params.requestedDecisions?.includes("allow-always") === true && !currentBinding ? params.requestedDecisions.filter((decision) => decision !== "allow-always") : params.requestedDecisions
};
}
function retainResolvedNodeInvokePlacementGrant(params) {
if (params.decision !== "allow-always" || !params.binding) return true;
if (!params.owner || !isBindingCurrentForOwner(params.owner, params.binding) || params.runtime?.validate(params.binding).outcome !== "consumed") return false;
params.authorization.binding = params.binding;
return true;
}
function consumeNodeInvokePlacementGrant(params) {
if (!params.authorization.binding) return "not-required";
try {
return params.runtime?.consume(params.authorization.binding).outcome === "consumed" ? "consumed" : "rejected";
} catch {
return "rejected";
}
}
//#endregion
//#region src/gateway/node-invoke-plugin-approval.ts
function sanitizeOptionalMeta(value) {
const normalized = normalizeOptionalString(value);
return normalized ? sanitizeExecApprovalDisplayText(normalized) : null;
}
function normalizeRouteThreadId(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
return normalizeOptionalString(value) ?? null;
}
function resolveNodeInvokeTurnSourceFields(turnSource) {
return {
turnSourceChannel: normalizeOptionalString(turnSource?.channel) ?? null,
turnSourceTo: normalizeOptionalString(turnSource?.to) ?? null,
turnSourceAccountId: normalizeOptionalString(turnSource?.accountId) ?? null,
turnSourceThreadId: normalizeRouteThreadId(turnSource?.threadId)
};
}
function createPluginNodeInvokeApprovalRuntime(params) {
const manager = params.context.pluginApprovalManager;
if (!manager) return;
return { async request(input) {
const timeoutMs = resolvePluginApprovalTimeoutMs(input.timeoutMs);
const turnSource = resolveNodeInvokeTurnSourceFields(params.turnSource);
const callerIdentity = params.callerIdentity;
const invocationSessionKey = params.client?.internal?.pluginRuntimeOwnerId === params.pluginId ? params.client.internal.nodeInvokeApprovalSessionKey : void 0;
if (!params.isCurrent()) throw new Error("agent runtime approval authority is no longer active");
const requestedDecisions = input.allowedDecisions === void 0 ? void 0 : resolveCanonicalPluginApprovalRequestAllowedDecisions({ allowedDecisions: input.allowedDecisions });
const scopedAuthority = params.placementGrantAuthority;
const placementGrantOwner = scopedAuthority ? {
agentId: scopedAuthority.agentId,
sessionKey: scopedAuthority.sessionKey,
assertCurrent: (binding) => scopedAuthority.assertCurrent({
pluginId: binding.pluginId,
command: binding.command,
nodeId: binding.nodeId,
workspace: {
workspaceDir: binding.cwd,
environmentId: binding.environmentId,
sessionId: binding.sessionId,
ownerEpoch: binding.ownerEpoch,
sessionKey: binding.sessionKey
}
})
} : void 0;
const placementGrantResolution = resolveNodeInvokePlacementGrant({
runtime: params.context.placementStandingGrants,
requestedDecisions,
owner: placementGrantOwner,
pluginId: params.pluginId,
command: params.command,
...params.approvalScope ? { approvalScope: params.approvalScope } : {},
risk: params.risk,
nodeSession: params.nodeSession
});
if (placementGrantResolution.kind === "granted") {
params.standingGrantAuthorization.binding = placementGrantResolution.binding;
return {
id: placementGrantResolution.approvalId,
decision: "allow-always"
};
}
const { allowedDecisions, binding: placementGrant } = placementGrantResolution;
const request = {
pluginId: params.pluginId,
title: truncateUtf16Safe(sanitizeExecApprovalDisplayText(normalizeOptionalString(input.title) ?? ""), 80),
description: truncateUtf16Safe(sanitizeExecApprovalWarningText(normalizeOptionalString(input.description) ?? ""), 256),
scope: input.scope ? sanitizeApprovalScope(input.scope) : null,
severity: input.severity ?? "warning",
...allowedDecisions === void 0 ? {} : { allowedDecisions },
toolName: sanitizeOptionalMeta(input.toolName),
toolCallId: normalizeOptionalString(input.toolCallId) ?? null,
agentId: callerIdentity?.agentId ?? scopedAuthority?.agentId ?? sanitizeOptionalMeta(input.agentId),
sessionKey: callerIdentity?.sessionKey ?? scopedAuthority?.sessionKey ?? invocationSessionKey ?? normalizeOptionalString(input.sessionKey) ?? null,
runId: callerIdentity?.operationalRunInstance.runId ?? scopedAuthority?.runId ?? null,
placementGrant,
turnSourceChannel: turnSource.turnSourceChannel,
turnSourceTo: turnSource.turnSourceTo,
turnSourceAccountId: turnSource.turnSourceAccountId,
turnSourceThreadId: turnSource.turnSourceThreadId
};
const record = manager.create(request, timeoutMs, `plugin:${randomUUID()}`);
if (callerIdentity) {
record.agentRuntimeDelegatedAuthority = callerIdentity.delegatedAuthority;
if (callerIdentity.executionIdentity) record.executionIdentityToken = callerIdentity.executionIdentity;
}
if (placementGrant && placementGrantOwner) record.approvalAuthority = () => {
placementGrantOwner.assertCurrent(placementGrant);
return true;
};
bindApprovalRequesterMetadata({
record,
client: params.client
});
const respond = () => {};
const decisionPromise = manager.register(record, timeoutMs);
const requestEvent = buildRequestedApprovalEvent(record, "plugin");
const forwardRequest = params.context.forwardPluginApprovalRequest;
const iosPushRequest = params.context.pluginApprovalIosPushDelivery?.handleRequested?.bind(params.context.pluginApprovalIosPushDelivery);
await handlePendingApprovalRequest({
manager,
record,
respond,
context: params.context,
requestEventName: "plugin.approval.requested",
requestEvent,
twoPhase: false,
approvalKind: "plugin",
deliverRequest: () => runApprovalRequestDeliveries({
context: params.context,
record,
forward: forwardRequest ? [() => forwardRequest(requestEvent), "plugin approvals: forward node policy request failed"] : void 0,
iosPush: iosPushRequest ? [(isTargetVisible) => iosPushRequest(requestEvent, { isTargetVisible }), "plugin approvals: iOS push node policy request failed"] : void 0
}),
afterDecision: async (decision) => {
if (decision === null) await params.context.pluginApprovalIosPushDelivery?.handleExpired?.(requestEvent);
},
afterDecisionErrorLabel: "plugin approvals: iOS push node policy expire failed"
});
let decision = manager.projectDecisionIfActive(record.id, await decisionPromise);
if (!params.isCurrent()) return {
id: record.id,
decision: null
};
if (decision === "allow-once" && !manager.consumeAllowOnce(record.id, `plugin.node.invoke:${record.id}`)) return {
id: record.id,
decision: null
};
decision = manager.projectDecisionIfActive(record.id, decision);
if (!retainResolvedNodeInvokePlacementGrant({
runtime: params.context.placementStandingGrants,
decision,
binding: placementGrant,
owner: placementGrantOwner,
authorization: params.standingGrantAuthorization
})) return {
id: record.id,
decision: null
};
return {
id: record.id,
decision
};
} };
}
//#endregion
//#region src/gateway/node-invoke-readiness.ts
const READINESS_RETRY_DELAYS_MS = [
100,
250,
500,
1e3
];
/** Recover only an explicit rejection before native execution, inside the RPC's authority. */
async function invokeNodeWithReadinessRetry(registry, request) {
let deadlineAtMs = request.timeoutMs !== void 0 && Number.isFinite(request.timeoutMs) && request.timeoutMs > 0 ? Date.now() + request.timeoutMs : void 0;
const timedOut = () => ({
ok: false,
error: {
code: "TIMEOUT",
message: "node invoke timed out"
}
});
for (let attempt = 0;; attempt += 1) {
const timeoutMs = deadlineAtMs === void 0 ? request.timeoutMs : Math.max(0, deadlineAtMs - Date.now());
if (deadlineAtMs !== void 0 && timeoutMs === 0) return timedOut();
const result = await registry.invoke({
...request,
timeoutMs,
onDispatchReady: (invokeId, dispatchDeadlineAtMs) => {
if (dispatchDeadlineAtMs !== void 0 && (deadlineAtMs === void 0 || dispatchDeadlineAtMs < deadlineAtMs)) deadlineAtMs = dispatchDeadlineAtMs;
request.onDispatchReady?.(invokeId, dispatchDeadlineAtMs);
}
});
const delayMs = READINESS_RETRY_DELAYS_MS[attempt];
if (result.ok || result.error?.code !== "NODE_NOT_READY" || delayMs === void 0) return result;
try {
await sleep(deadlineAtMs === void 0 ? delayMs : Math.min(delayMs, Math.max(0, deadlineAtMs - Date.now())), request.signal);
} catch (error) {
if (deadlineAtMs !== void 0 && Date.now() >= deadlineAtMs) return timedOut();
if (request.signal?.aborted) return {
ok: false,
error: {
code: "ABORTED",
message: "node invoke cancelled"
}
};
throw error;
}
}
}
//#endregion
//#region src/gateway/node-invoke-plugin-policy.ts
function parseScopes(client) {
return Array.isArray(client?.connect?.scopes) ? client.connect.scopes.filter((scope) => typeof scope === "string") : [];
}
function parsePayload(payloadJSON, payload) {
if (!payloadJSON) return payload;
try {
return JSON.parse(payloadJSON);
} catch {
return payload;
}
}
function findDangerousPluginNodeCommand(registry, command) {
const normalizedCommand = command.trim();
if (!normalizedCommand) return null;
return registry?.nodeHostCommands?.find((entry) => entry.command.dangerous === true && entry.command.command.trim() === normalizedCommand) ?? null;
}
function validateRiskClassification(value) {
const family = normalizeOptionalString(value?.family);
if (value?.level !== "ordinary" && value?.level !== "high" || !family || !/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(family)) return null;
return {
level: value.level,
family
};
}
function resolveStandingApprovalScope(value) {
const approval = asOptionalRecord(value);
const scope = normalizeOptionalString(approval?.scope);
return approval?.kind === "placement" && scope && /^[a-z0-9][a-z0-9._-]{0,63}$/u.test(scope) ? scope : void 0;
}
/** Applies the registered plugin policy for a node.invoke command, if one exists. */
async function applyPluginNodeInvokePolicy(params) {
const registry = getActivePluginGatewayNodePolicyRegistry();
const callerIdentity = params.agentRuntimeIdentity ?? params.client?.internal?.agentRuntimeIdentity;
const token = callerIdentity?.executionIdentity;
const isCallerRuntimeAuthorityActive = () => !callerIdentity || params.context.validateAgentRuntimeApprovalAuthority?.(callerIdentity) === true;
const decisionOccurrenceId = randomUUID();
let receiptOrdinal = 0;
const recordNodeDecision = (input) => {
receiptOrdinal += 1;
recordRuntimeActionDecision({
token,
family: "node",
operation: "invoke",
outcome: input.outcome,
coverageState: input.coverageState,
reasonCode: input.reasonCode,
owner: "node-runtime",
decisionBoundary: "gateway.node-invoke-plugin-policy",
policyRefs: [
"node:pairing",
"node:command-capability",
"plugin:node-invoke-policy"
],
summary: input.summary,
missingEvidence: input.missingEvidence,
remediation: input.remediation ?? [],
discriminator: JSON.stringify([
input.pluginId,
params.nodeSession.nodeId,
params.command,
decisionOccurrenceId,
receiptOrdinal
])
});
};
const trustedTurnSource = callerIdentity ? params.turnSource : void 0;
const entry = registry?.nodeInvokePolicies?.find((candidate) => candidate.policy.commands.includes(params.command));
if (!entry) {
const dangerousCommand = findDangerousPluginNodeCommand(registry, params.command);
if (dangerousCommand) {
recordNodeDecision({
pluginId: dangerousCommand.pluginId,
outcome: "denied",
coverageState: "enforced",
reasonCode: "node_plugin_policy_missing",
summary: "A dangerous plugin-owned node command was denied because its policy was missing."
});
return {
ok: false,
code: "PLUGIN_POLICY_MISSING",
message: `node.invoke ${params.command} is registered as dangerous by plugin ${dangerousCommand.pluginId} but has no plugin node.invoke policy`,
details: { nodeCommandDispatched: false }
};
}
return null;
}
let risk;
if (entry.policy.classifyRisk) {
try {
risk = validateRiskClassification(entry.policy.classifyRisk({
command: params.command,
params: params.params
})) ?? void 0;
} catch {}
if (!risk) {
recordNodeDecision({
pluginId: entry.pluginId,
outcome: "denied",
coverageState: "enforced",
reasonCode: "node_risk_classification_failed",
summary: "A plugin-owned node command was denied before transport after risk classification failed."
});
return {
ok: false,
code: "PLUGIN_POLICY_RISK_CLASSIFICATION_FAILED",
message: `node.invoke ${params.command} arguments could not be classified by plugin ${entry.pluginId}`,
details: { nodeCommandDispatched: false }
};
}
}
const approvalScope = resolveStandingApprovalScope(entry.policy.standingApproval);
let nodeCommandDispatched = false;
let nodeGateDecisionRecorded = false;
const standingGrantAuthorization = {};
const pluginRecord = registry?.plugins.find((record) => record.id === entry.pluginId);
const policy = entry.policy;
const isPluginCurrent = () => getActivePluginGatewayNodePolicyRegistry() === registry && entry.policy === policy && registry?.nodeInvokePolicies.includes(entry) === true && (!pluginRecord || registry.plugins.includes(pluginRecord) && pluginRecord.enabled && pluginRecord.status === "loaded");
const dispatchNode = async (override = {}, sessionAuthority) => {
const deny = (reasonCode, result) => {
nodeGateDecisionRecorded = true;
recordNodeDecision({
pluginId: entry.pluginId,
outcome: "denied",
coverageState: "enforced",
reasonCode,
summary: "A plugin-owned node command was denied at the Gateway dispatch gate."
});
return result;
};
sessionAuthority?.assertCurrent();
if (!isCallerRuntimeAuthorityActive()) return deny("node_runtime_authority_closed", {
ok: false,
code: "APPROVAL_AUTHORITY_CLOSED",
message: "agent runtime approval authority closed before node dispatch"
});
if (params.isInvocationCurrent && !await params.isInvocationCurrent()) return deny("node_pairing_changed", {
ok: false,
code: "PAIRING_CHANGED",
message: "node pairing changed before dispatch"
});
const currentNode = params.nodeSession.pairingGeneration ? params.context.nodeRegistry.getForPairingGeneration(params.nodeSession.nodeId, params.nodeSession.pairingGeneration) : params.context.nodeRegistry.get(params.nodeSession.nodeId);
if (!currentNode || currentNode.connId !== params.nodeSession.connId) return deny("node_route_changed", {
ok: false,
code: "ROUTE_CHANGED",
message: "node connection changed before dispatch"
});
if (currentNode.client.invalidated === true) return deny("node_pairing_changed", {
ok: false,
code: "PAIRING_CHANGED",
message: "node pairing changed before dispatch"
});
const resolveCommandAuthorization = () => {
const declaredCommands = params.privateTransport?.commands ? [...params.privateTransport.commands] : currentNode.commands;
return isNodeCommandAllowed({
command: params.command,
declaredCommands,
allowlist: resolveNodeCommandAllowlist(params.context.getRuntimeConfig(), {
...currentNode,
approvedCommands: declaredCommands
})
});
};
const allowed = resolveCommandAuthorization();
if (!allowed.ok) return deny("node_command_revoked", {
ok: false,
code: "NODE_COMMAND_REVOKED",
message: `node command not allowed at dispatch: ${allowed.reason}`,
details: {
command: params.command,
reason: allowed.reason
}
});
const remainingTimeoutMs = params.resolveRemainingTimeoutMs?.();
if (remainingTimeoutMs === 0 && params.timeoutMs !== 0) return deny("node_dispatch_timeout", {
ok: false,
code: "TIMEOUT",
message: "node invoke timed out"
});
const requestedTimeoutMs = override.timeoutMs ?? params.timeoutMs;
const timeoutMs = typeof remainingTimeoutMs === "number" && remainingTimeoutMs > 0 ? typeof requestedTimeoutMs === "number" && requestedTimeoutMs > 0 ? Math.min(requestedTimeoutMs, remainingTimeoutMs) : remainingTimeoutMs : requestedTimeoutMs;
sessionAuthority?.assertCurrent();
if (params.privateTransport?.isCurrent() === false) return deny("node_private_owner_closed", {
ok: false,
code: "PRIVATE_OWNER_CLOSED",
message: "private node invocation owner closed before dispatch"
});
if (!isPluginCurrent()) return deny("node_plugin_replaced", {
ok: false,
code: "PLUGIN_POLICY_CHANGED",
message: "node plugin policy changed before dispatch"
});
if (!isCallerRuntimeAuthorityActive()) return deny("node_runtime_authority_closed", {
ok: false,
code: "APPROVAL_AUTHORITY_CLOSED",
message: "agent runtime approval authority closed before node dispatch"
});
if (params.isApprovalAuthorityActive?.() === false) return deny("node_approval_authority_closed", {
ok: false,
code: "APPROVAL_AUTHORITY_CLOSED",
message: "approved runtime authority closed before node dispatch"
});
recordNodeDecision({
pluginId: entry.pluginId,
outcome: "allowed",
coverageState: "enforced",
reasonCode: "node_dispatch_gate_allowed",
summary: "Gateway node pairing, capability, and plugin policy gates allowed transport dispatch."
});
nodeGateDecisionRecorded = true;
const request = {
nodeId: params.nodeSession.nodeId,
expectedConnId: params.nodeSession.connId,
...params.nodeSession.pairingGeneration ? { expectedPairingGeneration: params.nodeSession.pairingGeneration } : {},
command: params.command,
params: override.params ?? params.params,
timeoutMs,
...sessionAuthority ? { signal: params.signal ? AbortSignal.any([params.signal, sessionAuthority.signal]) : sessionAuthority.signal } : params.signal ? { signal: params.signal } : {},
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
idempotencyKey: override.idempotencyKey ?? params.idempotencyKey,
...params.nodeInvokeStream && {
onProgress: params.nodeInvokeStream.onProgress,
idleTimeoutMs: params.nodeInvokeStream.idleTimeoutMs
},
isDispatchAuthorized: () => {
try {
sessionAuthority?.assertCurrent();
} catch {
return false;
}
if (!(isPluginCurrent() && params.privateTransport?.isCurrent() !== false && (params.nodeInvokeStream?.isRuntimeCurrent() ?? true) && isCallerRuntimeAuthorityActive() && params.isApprovalAuthorityActive?.() !== false && resolveCommandAuthorization().ok)) return false;
const grantOutcome = consumeNodeInvokePlacementGrant({
runtime: params.context.placementStandingGrants,
authorization: standingGrantAuthorization
});
if (grantOutcome === "rejected") return false;
if (grantOutcome === "consumed") recordNodeDecision({
pluginId: entry.pluginId,
outcome: "allowed",
coverageState: "enforced",
reasonCode: "node_placement_standing_grant_consumed",
summary: "A placement-scoped standing grant authorized the node transport dispatch."
});
return true;
},
onDispatchReady: (invokeId) => {
nodeCommandDispatched = true;
params.onNodeCommandDispatched?.();
params.nodeInvokeStream?.onDispatchReady(invokeId);
}
};
const res = params.privateTransport ? await params.privateTransport.invoke(request) : await invokeNodeWithReadinessRetry(params.context.nodeRegistry, request);
if (!res.ok) {
if (nodeCommandDispatched) recordNodeDecision({
pluginId: entry.pluginId,
outcome: "unknown",
coverageState: "unknown",
reasonCode: "node_action_completion_unknown",
summary: "The node transport accepted the action but did not report a successful outcome.",
missingEvidence: ["node.action_completion"],
remediation: [{
code: "inspect_node_action",
text: "Inspect the paired node before retrying an action whose completion is unknown."
}]
});
return {
ok: false,
code: res.error?.code,
message: res.error?.message ?? "node command failed",
details: { nodeError: res.error ?? null }
};
}
recordNodeDecision({
pluginId: entry.pluginId,
outcome: "allowed",
coverageState: "attribution-only",
reasonCode: "node_action_completed",
summary: "The paired node reported successful completion; this is attribution, not authorization."
});
return {
ok: true,
payload: parsePayload(res.payloadJSON, res.payload),
payloadJSON: res.payloadJSON ?? null
};
};
const scope = getPluginRuntimeGatewayRequestScope();
const ownedInvocation = params.nodeInvokeStream && params.client?.internal?.pluginRuntimeOwnerId === entry.pluginId ? scope?.invokeWithSessionNodeAuthority : void 0;
const invokeOwned = (source, override, createParams) => ownedInvocation && override.workspace ? ownedInvocation({
pluginId: entry.pluginId,
command: params.command,
nodeId: params.nodeSession.nodeId,
workspace: override.workspace,
source
}, async (assertCurrent, signal) => {
if (params.sessionKey !== override.workspace?.sessionKey) throw new Error("Node launch requires its authenticated outer session");
return await dispatchNode(createParams ? {
...override,
params: createParams()
} : override, {
assertCurrent,
signal
});
}) : void 0;
const invokeNode = async (override = {}) => {
if (!ownedInvocation || !override.workspace) return await dispatchNode(override);
const result = await invokeOwned("human-approved", override);
if (!result) throw new Error("Node launch lost its admitted owner");
return result;
};
let result;
try {
result = await entry.policy.handle({
nodeId: params.nodeSession.nodeId,
command: params.command,
params: params.params,
timeoutMs: params.timeoutMs,
idempotencyKey: params.idempotencyKey,
config: params.context.getRuntimeConfig(),
pluginConfig: entry.pluginConfig,
node: {
nodeId: params.nodeSession.nodeId,
displayName: params.nodeSession.displayName,
platform: params.nodeSession.platform,
deviceFamily: params.nodeSession.deviceFamily,
commands: params.nodeSession.commands
},
client: params.client ? {
connId: params.client.connId,
scopes: parseScopes(params.client)
} : null,
...risk ? { risk } : {},
approvals: createPluginNodeInvokeApprovalRuntime({
context: params.context,
client: params.client,
callerIdentity,
pluginId: entry.pluginId,
command: params.command,
...approvalScope ? { approvalScope } : {},
nodeSession: params.nodeSession,
risk,
standingGrantAuthorization,
...scope?.nodePlacementGrantAuthority ? { placementGrantAuthority: scope.nodePlacementGrantAuthority } : {},
turnSource: trustedTurnSource,
isCurrent: () => isPluginCurrent() && isCallerRuntimeAuthorityActive() && params.privateTransport?.isCurrent() !== false && params.isApprovalAuthorityActive?.() !== false
}),
invokeNode,
...ownedInvocation ? { invokeNodeWithSessionFull: async ({ workspace, createParams }) => await invokeOwned("session-full", { workspace }, createParams) } : {}
});
} catch (error) {
if (!(error instanceof ApprovalObserverClosedError) && !nodeCommandDispatched && isCallerRuntimeAuthorityActive()) recordNodeDecision({
pluginId: entry.pluginId,
outcome: "denied",
coverageState: "enforced",
reasonCode: "node_plugin_policy_failed",
summary: "The registered plugin policy failed closed before node transport dispatch."
});
throw error;
}
if (!nodeCommandDispatched && !nodeGateDecisionRecorded && isCallerRuntimeAuthorityActive()) recordNodeDecision({
pluginId: entry.pluginId,
outcome: result.ok ? "unknown" : "denied",
coverageState: result.ok ? "unknown" : "enforced",
reasonCode: result.ok ? "node_action_callback_missing" : "node_plugin_policy_denied",
summary: result.ok ? "The plugin policy returned without invoking the expected OpenClaw node callback." : "The registered plugin policy denied node transport dispatch.",
missingEvidence: result.ok ? ["node.action_callback"] : [],
remediation: result.ok ? [{
code: "add_node_action_callback",
text: "Route the native action through the provided OpenClaw node callback."
}] : []
});
return result.ok ? result : {
...result,
details: {
...result.details,
nodeCommandDispatched
}
};
}
//#endregion
export { invokeNodeWithReadinessRetry as n, applyPluginNodeInvokePolicy as t };