mirror of https://github.com/openclaw/openclaw.git
refactor: share exec host approval helpers
This commit is contained in:
parent
3bc9d9177d
commit
6720bf5be0
|
|
@ -1,5 +1,4 @@
|
||||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||||
import { buildExecApprovalUnavailableReplyPayload } from "../infra/exec-approval-reply.js";
|
|
||||||
import {
|
import {
|
||||||
addAllowlistEntry,
|
addAllowlistEntry,
|
||||||
type ExecAsk,
|
type ExecAsk,
|
||||||
|
|
@ -14,20 +13,22 @@ import { detectCommandObfuscation } from "../infra/exec-obfuscation-detect.js";
|
||||||
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
|
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
|
||||||
import { logInfo } from "../logger.js";
|
import { logInfo } from "../logger.js";
|
||||||
import { markBackgrounded, tail } from "./bash-process-registry.js";
|
import { markBackgrounded, tail } from "./bash-process-registry.js";
|
||||||
import { sendExecApprovalFollowup } from "./bash-tools.exec-approval-followup.js";
|
|
||||||
import {
|
import {
|
||||||
buildExecApprovalRequesterContext,
|
buildExecApprovalRequesterContext,
|
||||||
buildExecApprovalTurnSourceContext,
|
buildExecApprovalTurnSourceContext,
|
||||||
registerExecApprovalRequestForHostOrThrow,
|
registerExecApprovalRequestForHostOrThrow,
|
||||||
} from "./bash-tools.exec-approval-request.js";
|
} from "./bash-tools.exec-approval-request.js";
|
||||||
import {
|
import {
|
||||||
|
buildDefaultExecApprovalRequestArgs,
|
||||||
|
buildExecApprovalFollowupTarget,
|
||||||
|
buildExecApprovalPendingToolResult,
|
||||||
|
createExecApprovalDecisionState,
|
||||||
createAndRegisterDefaultExecApprovalRequest,
|
createAndRegisterDefaultExecApprovalRequest,
|
||||||
resolveBaseExecApprovalDecision,
|
|
||||||
resolveApprovalDecisionOrUndefined,
|
resolveApprovalDecisionOrUndefined,
|
||||||
resolveExecHostApprovalContext,
|
resolveExecHostApprovalContext,
|
||||||
|
sendExecApprovalFollowupResult,
|
||||||
} from "./bash-tools.exec-host-shared.js";
|
} from "./bash-tools.exec-host-shared.js";
|
||||||
import {
|
import {
|
||||||
buildApprovalPendingMessage,
|
|
||||||
DEFAULT_NOTIFY_TAIL_CHARS,
|
DEFAULT_NOTIFY_TAIL_CHARS,
|
||||||
createApprovalSlug,
|
createApprovalSlug,
|
||||||
normalizeNotifyOutput,
|
normalizeNotifyOutput,
|
||||||
|
|
@ -140,6 +141,28 @@ export async function processGatewayAllowlist(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiresAsk) {
|
if (requiresAsk) {
|
||||||
|
const requestArgs = buildDefaultExecApprovalRequestArgs({
|
||||||
|
warnings: params.warnings,
|
||||||
|
approvalRunningNoticeMs: params.approvalRunningNoticeMs,
|
||||||
|
createApprovalSlug,
|
||||||
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
|
});
|
||||||
|
const registerGatewayApproval = async (approvalId: string) =>
|
||||||
|
await registerExecApprovalRequestForHostOrThrow({
|
||||||
|
approvalId,
|
||||||
|
command: params.command,
|
||||||
|
workdir: params.workdir,
|
||||||
|
host: "gateway",
|
||||||
|
security: hostSecurity,
|
||||||
|
ask: hostAsk,
|
||||||
|
...buildExecApprovalRequesterContext({
|
||||||
|
agentId: params.agentId,
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
}),
|
||||||
|
resolvedPath: allowlistEval.segments[0]?.resolution?.resolvedPath,
|
||||||
|
...buildExecApprovalTurnSourceContext(params),
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
approvalId,
|
approvalId,
|
||||||
approvalSlug,
|
approvalSlug,
|
||||||
|
|
@ -150,57 +173,46 @@ export async function processGatewayAllowlist(
|
||||||
sentApproverDms,
|
sentApproverDms,
|
||||||
unavailableReason,
|
unavailableReason,
|
||||||
} = await createAndRegisterDefaultExecApprovalRequest({
|
} = await createAndRegisterDefaultExecApprovalRequest({
|
||||||
warnings: params.warnings,
|
...requestArgs,
|
||||||
approvalRunningNoticeMs: params.approvalRunningNoticeMs,
|
register: registerGatewayApproval,
|
||||||
createApprovalSlug,
|
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
register: async (approvalId) =>
|
|
||||||
await registerExecApprovalRequestForHostOrThrow({
|
|
||||||
approvalId,
|
|
||||||
command: params.command,
|
|
||||||
workdir: params.workdir,
|
|
||||||
host: "gateway",
|
|
||||||
security: hostSecurity,
|
|
||||||
ask: hostAsk,
|
|
||||||
...buildExecApprovalRequesterContext({
|
|
||||||
agentId: params.agentId,
|
|
||||||
sessionKey: params.sessionKey,
|
|
||||||
}),
|
|
||||||
resolvedPath: allowlistEval.segments[0]?.resolution?.resolvedPath,
|
|
||||||
...buildExecApprovalTurnSourceContext(params),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
const resolvedPath = allowlistEval.segments[0]?.resolution?.resolvedPath;
|
const resolvedPath = allowlistEval.segments[0]?.resolution?.resolvedPath;
|
||||||
const effectiveTimeout =
|
const effectiveTimeout =
|
||||||
typeof params.timeoutSec === "number" ? params.timeoutSec : params.defaultTimeoutSec;
|
typeof params.timeoutSec === "number" ? params.timeoutSec : params.defaultTimeoutSec;
|
||||||
|
const followupTarget = buildExecApprovalFollowupTarget({
|
||||||
|
approvalId,
|
||||||
|
sessionKey: params.notifySessionKey,
|
||||||
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceTo: params.turnSourceTo,
|
||||||
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
|
turnSourceThreadId: params.turnSourceThreadId,
|
||||||
|
});
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const decision = await resolveApprovalDecisionOrUndefined({
|
const decision = await resolveApprovalDecisionOrUndefined({
|
||||||
approvalId,
|
approvalId,
|
||||||
preResolvedDecision,
|
preResolvedDecision,
|
||||||
onFailure: () =>
|
onFailure: () =>
|
||||||
void sendExecApprovalFollowup({
|
void sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (gateway id=${approvalId}, approval-request-failed): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
),
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (gateway id=${approvalId}, approval-request-failed): ${params.command}`,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if (decision === undefined) {
|
if (decision === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseDecision = resolveBaseExecApprovalDecision({
|
const {
|
||||||
|
baseDecision,
|
||||||
|
approvedByAsk: initialApprovedByAsk,
|
||||||
|
deniedReason: initialDeniedReason,
|
||||||
|
} = createExecApprovalDecisionState({
|
||||||
decision,
|
decision,
|
||||||
askFallback,
|
askFallback,
|
||||||
obfuscationDetected: obfuscation.detected,
|
obfuscationDetected: obfuscation.detected,
|
||||||
});
|
});
|
||||||
let approvedByAsk = baseDecision.approvedByAsk;
|
let approvedByAsk = initialApprovedByAsk;
|
||||||
let deniedReason = baseDecision.deniedReason;
|
let deniedReason = initialDeniedReason;
|
||||||
|
|
||||||
if (baseDecision.timedOut && askFallback === "allowlist") {
|
if (baseDecision.timedOut && askFallback === "allowlist") {
|
||||||
if (!analysisOk || !allowlistSatisfied) {
|
if (!analysisOk || !allowlistSatisfied) {
|
||||||
|
|
@ -232,15 +244,10 @@ export async function processGatewayAllowlist(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deniedReason) {
|
if (deniedReason) {
|
||||||
await sendExecApprovalFollowup({
|
await sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (gateway id=${approvalId}, ${deniedReason}): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
);
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (gateway id=${approvalId}, ${deniedReason}): ${params.command}`,
|
|
||||||
}).catch(() => {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,15 +273,10 @@ export async function processGatewayAllowlist(
|
||||||
timeoutSec: effectiveTimeout,
|
timeoutSec: effectiveTimeout,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
await sendExecApprovalFollowup({
|
await sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (gateway id=${approvalId}, spawn-failed): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
);
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (gateway id=${approvalId}, spawn-failed): ${params.command}`,
|
|
||||||
}).catch(() => {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,63 +290,22 @@ export async function processGatewayAllowlist(
|
||||||
const summary = output
|
const summary = output
|
||||||
? `Exec finished (gateway id=${approvalId}, session=${run.session.id}, ${exitLabel})\n${output}`
|
? `Exec finished (gateway id=${approvalId}, session=${run.session.id}, ${exitLabel})\n${output}`
|
||||||
: `Exec finished (gateway id=${approvalId}, session=${run.session.id}, ${exitLabel})`;
|
: `Exec finished (gateway id=${approvalId}, session=${run.session.id}, ${exitLabel})`;
|
||||||
await sendExecApprovalFollowup({
|
await sendExecApprovalFollowupResult(followupTarget, summary);
|
||||||
approvalId,
|
|
||||||
sessionKey: params.notifySessionKey,
|
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: summary,
|
|
||||||
}).catch(() => {});
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pendingResult: {
|
pendingResult: buildExecApprovalPendingToolResult({
|
||||||
content: [
|
host: "gateway",
|
||||||
{
|
command: params.command,
|
||||||
type: "text",
|
cwd: params.workdir,
|
||||||
text:
|
warningText,
|
||||||
unavailableReason !== null
|
approvalId,
|
||||||
? (buildExecApprovalUnavailableReplyPayload({
|
approvalSlug,
|
||||||
warningText,
|
expiresAtMs,
|
||||||
reason: unavailableReason,
|
initiatingSurface,
|
||||||
channelLabel: initiatingSurface.channelLabel,
|
sentApproverDms,
|
||||||
sentApproverDms,
|
unavailableReason,
|
||||||
}).text ?? "")
|
}),
|
||||||
: buildApprovalPendingMessage({
|
|
||||||
warningText,
|
|
||||||
approvalSlug,
|
|
||||||
approvalId,
|
|
||||||
command: params.command,
|
|
||||||
cwd: params.workdir,
|
|
||||||
host: "gateway",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
details:
|
|
||||||
unavailableReason !== null
|
|
||||||
? ({
|
|
||||||
status: "approval-unavailable",
|
|
||||||
reason: unavailableReason,
|
|
||||||
channelLabel: initiatingSurface.channelLabel,
|
|
||||||
sentApproverDms,
|
|
||||||
host: "gateway",
|
|
||||||
command: params.command,
|
|
||||||
cwd: params.workdir,
|
|
||||||
warningText,
|
|
||||||
} satisfies ExecToolDetails)
|
|
||||||
: ({
|
|
||||||
status: "approval-pending",
|
|
||||||
approvalId,
|
|
||||||
approvalSlug,
|
|
||||||
expiresAtMs,
|
|
||||||
host: "gateway",
|
|
||||||
command: params.command,
|
|
||||||
cwd: params.workdir,
|
|
||||||
warningText,
|
|
||||||
} satisfies ExecToolDetails),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||||
import { buildExecApprovalUnavailableReplyPayload } from "../infra/exec-approval-reply.js";
|
|
||||||
import {
|
import {
|
||||||
type ExecApprovalsFile,
|
type ExecApprovalsFile,
|
||||||
type ExecAsk,
|
type ExecAsk,
|
||||||
|
|
@ -13,20 +12,13 @@ import { detectCommandObfuscation } from "../infra/exec-obfuscation-detect.js";
|
||||||
import { buildNodeShellCommand } from "../infra/node-shell.js";
|
import { buildNodeShellCommand } from "../infra/node-shell.js";
|
||||||
import { parsePreparedSystemRunPayload } from "../infra/system-run-approval-context.js";
|
import { parsePreparedSystemRunPayload } from "../infra/system-run-approval-context.js";
|
||||||
import { logInfo } from "../logger.js";
|
import { logInfo } from "../logger.js";
|
||||||
import { sendExecApprovalFollowup } from "./bash-tools.exec-approval-followup.js";
|
|
||||||
import {
|
import {
|
||||||
buildExecApprovalRequesterContext,
|
buildExecApprovalRequesterContext,
|
||||||
buildExecApprovalTurnSourceContext,
|
buildExecApprovalTurnSourceContext,
|
||||||
registerExecApprovalRequestForHostOrThrow,
|
registerExecApprovalRequestForHostOrThrow,
|
||||||
} from "./bash-tools.exec-approval-request.js";
|
} from "./bash-tools.exec-approval-request.js";
|
||||||
|
import * as execHostShared from "./bash-tools.exec-host-shared.js";
|
||||||
import {
|
import {
|
||||||
createAndRegisterDefaultExecApprovalRequest,
|
|
||||||
resolveBaseExecApprovalDecision,
|
|
||||||
resolveApprovalDecisionOrUndefined,
|
|
||||||
resolveExecHostApprovalContext,
|
|
||||||
} from "./bash-tools.exec-host-shared.js";
|
|
||||||
import {
|
|
||||||
buildApprovalPendingMessage,
|
|
||||||
DEFAULT_NOTIFY_TAIL_CHARS,
|
DEFAULT_NOTIFY_TAIL_CHARS,
|
||||||
createApprovalSlug,
|
createApprovalSlug,
|
||||||
normalizeNotifyOutput,
|
normalizeNotifyOutput,
|
||||||
|
|
@ -61,7 +53,7 @@ export type ExecuteNodeHostCommandParams = {
|
||||||
export async function executeNodeHostCommand(
|
export async function executeNodeHostCommand(
|
||||||
params: ExecuteNodeHostCommandParams,
|
params: ExecuteNodeHostCommandParams,
|
||||||
): Promise<AgentToolResult<ExecToolDetails>> {
|
): Promise<AgentToolResult<ExecToolDetails>> {
|
||||||
const { hostSecurity, hostAsk, askFallback } = resolveExecHostApprovalContext({
|
const { hostSecurity, hostAsk, askFallback } = execHostShared.resolveExecHostApprovalContext({
|
||||||
agentId: params.agentId,
|
agentId: params.agentId,
|
||||||
security: params.security,
|
security: params.security,
|
||||||
ask: params.ask,
|
ask: params.ask,
|
||||||
|
|
@ -216,6 +208,29 @@ export async function executeNodeHostCommand(
|
||||||
}) satisfies Record<string, unknown>;
|
}) satisfies Record<string, unknown>;
|
||||||
|
|
||||||
if (requiresAsk) {
|
if (requiresAsk) {
|
||||||
|
const requestArgs = execHostShared.buildDefaultExecApprovalRequestArgs({
|
||||||
|
warnings: params.warnings,
|
||||||
|
approvalRunningNoticeMs: params.approvalRunningNoticeMs,
|
||||||
|
createApprovalSlug,
|
||||||
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
|
});
|
||||||
|
const registerNodeApproval = async (approvalId: string) =>
|
||||||
|
await registerExecApprovalRequestForHostOrThrow({
|
||||||
|
approvalId,
|
||||||
|
systemRunPlan: prepared.plan,
|
||||||
|
env: nodeEnv,
|
||||||
|
workdir: runCwd,
|
||||||
|
host: "node",
|
||||||
|
nodeId,
|
||||||
|
security: hostSecurity,
|
||||||
|
ask: hostAsk,
|
||||||
|
...buildExecApprovalRequesterContext({
|
||||||
|
agentId: runAgentId,
|
||||||
|
sessionKey: runSessionKey,
|
||||||
|
}),
|
||||||
|
...buildExecApprovalTurnSourceContext(params),
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
approvalId,
|
approvalId,
|
||||||
approvalSlug,
|
approvalSlug,
|
||||||
|
|
@ -225,57 +240,45 @@ export async function executeNodeHostCommand(
|
||||||
initiatingSurface,
|
initiatingSurface,
|
||||||
sentApproverDms,
|
sentApproverDms,
|
||||||
unavailableReason,
|
unavailableReason,
|
||||||
} = await createAndRegisterDefaultExecApprovalRequest({
|
} = await execHostShared.createAndRegisterDefaultExecApprovalRequest({
|
||||||
warnings: params.warnings,
|
...requestArgs,
|
||||||
approvalRunningNoticeMs: params.approvalRunningNoticeMs,
|
register: registerNodeApproval,
|
||||||
createApprovalSlug,
|
});
|
||||||
|
const followupTarget = execHostShared.buildExecApprovalFollowupTarget({
|
||||||
|
approvalId,
|
||||||
|
sessionKey: params.notifySessionKey,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceTo: params.turnSourceTo,
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
register: async (approvalId) =>
|
turnSourceThreadId: params.turnSourceThreadId,
|
||||||
await registerExecApprovalRequestForHostOrThrow({
|
|
||||||
approvalId,
|
|
||||||
systemRunPlan: prepared.plan,
|
|
||||||
env: nodeEnv,
|
|
||||||
workdir: runCwd,
|
|
||||||
host: "node",
|
|
||||||
nodeId,
|
|
||||||
security: hostSecurity,
|
|
||||||
ask: hostAsk,
|
|
||||||
...buildExecApprovalRequesterContext({
|
|
||||||
agentId: runAgentId,
|
|
||||||
sessionKey: runSessionKey,
|
|
||||||
}),
|
|
||||||
...buildExecApprovalTurnSourceContext(params),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const decision = await resolveApprovalDecisionOrUndefined({
|
const decision = await execHostShared.resolveApprovalDecisionOrUndefined({
|
||||||
approvalId,
|
approvalId,
|
||||||
preResolvedDecision,
|
preResolvedDecision,
|
||||||
onFailure: () =>
|
onFailure: () =>
|
||||||
void sendExecApprovalFollowup({
|
void execHostShared.sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (node=${nodeId} id=${approvalId}, approval-request-failed): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
),
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (node=${nodeId} id=${approvalId}, approval-request-failed): ${params.command}`,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if (decision === undefined) {
|
if (decision === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseDecision = resolveBaseExecApprovalDecision({
|
const {
|
||||||
|
baseDecision,
|
||||||
|
approvedByAsk: initialApprovedByAsk,
|
||||||
|
deniedReason: initialDeniedReason,
|
||||||
|
} = execHostShared.createExecApprovalDecisionState({
|
||||||
decision,
|
decision,
|
||||||
askFallback,
|
askFallback,
|
||||||
obfuscationDetected: obfuscation.detected,
|
obfuscationDetected: obfuscation.detected,
|
||||||
});
|
});
|
||||||
let approvedByAsk = baseDecision.approvedByAsk;
|
let approvedByAsk = initialApprovedByAsk;
|
||||||
let approvalDecision: "allow-once" | "allow-always" | null = null;
|
let approvalDecision: "allow-once" | "allow-always" | null = null;
|
||||||
let deniedReason = baseDecision.deniedReason;
|
let deniedReason = initialDeniedReason;
|
||||||
|
|
||||||
if (baseDecision.timedOut && askFallback === "full" && approvedByAsk) {
|
if (baseDecision.timedOut && askFallback === "full" && approvedByAsk) {
|
||||||
approvalDecision = "allow-once";
|
approvalDecision = "allow-once";
|
||||||
|
|
@ -288,15 +291,10 @@ export async function executeNodeHostCommand(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deniedReason) {
|
if (deniedReason) {
|
||||||
await sendExecApprovalFollowup({
|
await execHostShared.sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (node=${nodeId} id=${approvalId}, ${deniedReason}): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
);
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (node=${nodeId} id=${approvalId}, ${deniedReason}): ${params.command}`,
|
|
||||||
}).catch(() => {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,76 +328,28 @@ export async function executeNodeHostCommand(
|
||||||
const summary = output
|
const summary = output
|
||||||
? `Exec finished (node=${nodeId} id=${approvalId}, ${exitLabel})\n${output}`
|
? `Exec finished (node=${nodeId} id=${approvalId}, ${exitLabel})\n${output}`
|
||||||
: `Exec finished (node=${nodeId} id=${approvalId}, ${exitLabel})`;
|
: `Exec finished (node=${nodeId} id=${approvalId}, ${exitLabel})`;
|
||||||
await sendExecApprovalFollowup({
|
await execHostShared.sendExecApprovalFollowupResult(followupTarget, summary);
|
||||||
approvalId,
|
|
||||||
sessionKey: params.notifySessionKey,
|
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: summary,
|
|
||||||
}).catch(() => {});
|
|
||||||
} catch {
|
} catch {
|
||||||
await sendExecApprovalFollowup({
|
await execHostShared.sendExecApprovalFollowupResult(
|
||||||
approvalId,
|
followupTarget,
|
||||||
sessionKey: params.notifySessionKey,
|
`Exec denied (node=${nodeId} id=${approvalId}, invoke-failed): ${params.command}`,
|
||||||
turnSourceChannel: params.turnSourceChannel,
|
);
|
||||||
turnSourceTo: params.turnSourceTo,
|
|
||||||
turnSourceAccountId: params.turnSourceAccountId,
|
|
||||||
turnSourceThreadId: params.turnSourceThreadId,
|
|
||||||
resultText: `Exec denied (node=${nodeId} id=${approvalId}, invoke-failed): ${params.command}`,
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return execHostShared.buildExecApprovalPendingToolResult({
|
||||||
content: [
|
host: "node",
|
||||||
{
|
command: params.command,
|
||||||
type: "text",
|
cwd: params.workdir,
|
||||||
text:
|
warningText,
|
||||||
unavailableReason !== null
|
approvalId,
|
||||||
? (buildExecApprovalUnavailableReplyPayload({
|
approvalSlug,
|
||||||
warningText,
|
expiresAtMs,
|
||||||
reason: unavailableReason,
|
initiatingSurface,
|
||||||
channelLabel: initiatingSurface.channelLabel,
|
sentApproverDms,
|
||||||
sentApproverDms,
|
unavailableReason,
|
||||||
}).text ?? "")
|
nodeId,
|
||||||
: buildApprovalPendingMessage({
|
});
|
||||||
warningText,
|
|
||||||
approvalSlug,
|
|
||||||
approvalId,
|
|
||||||
command: prepared.plan.commandText,
|
|
||||||
cwd: runCwd,
|
|
||||||
host: "node",
|
|
||||||
nodeId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
details:
|
|
||||||
unavailableReason !== null
|
|
||||||
? ({
|
|
||||||
status: "approval-unavailable",
|
|
||||||
reason: unavailableReason,
|
|
||||||
channelLabel: initiatingSurface.channelLabel,
|
|
||||||
sentApproverDms,
|
|
||||||
host: "node",
|
|
||||||
command: params.command,
|
|
||||||
cwd: params.workdir,
|
|
||||||
nodeId,
|
|
||||||
warningText,
|
|
||||||
} satisfies ExecToolDetails)
|
|
||||||
: ({
|
|
||||||
status: "approval-pending",
|
|
||||||
approvalId,
|
|
||||||
approvalSlug,
|
|
||||||
expiresAtMs,
|
|
||||||
host: "node",
|
|
||||||
command: params.command,
|
|
||||||
cwd: params.workdir,
|
|
||||||
nodeId,
|
|
||||||
warningText,
|
|
||||||
} satisfies ExecToolDetails),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
|
import { buildExecApprovalUnavailableReplyPayload } from "../infra/exec-approval-reply.js";
|
||||||
import {
|
import {
|
||||||
hasConfiguredExecApprovalDmRoute,
|
hasConfiguredExecApprovalDmRoute,
|
||||||
type ExecApprovalInitiatingSurfaceState,
|
type ExecApprovalInitiatingSurfaceState,
|
||||||
|
|
@ -12,11 +14,14 @@ import {
|
||||||
type ExecAsk,
|
type ExecAsk,
|
||||||
type ExecSecurity,
|
type ExecSecurity,
|
||||||
} from "../infra/exec-approvals.js";
|
} from "../infra/exec-approvals.js";
|
||||||
|
import { sendExecApprovalFollowup } from "./bash-tools.exec-approval-followup.js";
|
||||||
import {
|
import {
|
||||||
type ExecApprovalRegistration,
|
type ExecApprovalRegistration,
|
||||||
resolveRegisteredExecApprovalDecision,
|
resolveRegisteredExecApprovalDecision,
|
||||||
} from "./bash-tools.exec-approval-request.js";
|
} from "./bash-tools.exec-approval-request.js";
|
||||||
|
import { buildApprovalPendingMessage } from "./bash-tools.exec-runtime.js";
|
||||||
import { DEFAULT_APPROVAL_TIMEOUT_MS } from "./bash-tools.exec-runtime.js";
|
import { DEFAULT_APPROVAL_TIMEOUT_MS } from "./bash-tools.exec-runtime.js";
|
||||||
|
import type { ExecToolDetails } from "./bash-tools.exec-types.js";
|
||||||
|
|
||||||
type ResolvedExecApprovals = ReturnType<typeof resolveExecApprovals>;
|
type ResolvedExecApprovals = ReturnType<typeof resolveExecApprovals>;
|
||||||
|
|
||||||
|
|
@ -53,6 +58,23 @@ export type RegisteredExecApprovalRequestContext = {
|
||||||
unavailableReason: ExecApprovalUnavailableReason | null;
|
unavailableReason: ExecApprovalUnavailableReason | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExecApprovalFollowupTarget = {
|
||||||
|
approvalId: string;
|
||||||
|
sessionKey?: string;
|
||||||
|
turnSourceChannel?: string;
|
||||||
|
turnSourceTo?: string;
|
||||||
|
turnSourceAccountId?: string;
|
||||||
|
turnSourceThreadId?: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DefaultExecApprovalRequestArgs = {
|
||||||
|
warnings: string[];
|
||||||
|
approvalRunningNoticeMs: number;
|
||||||
|
createApprovalSlug: (approvalId: string) => string;
|
||||||
|
turnSourceChannel?: string;
|
||||||
|
turnSourceAccountId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function createExecApprovalPendingState(params: {
|
export function createExecApprovalPendingState(params: {
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
|
|
@ -257,3 +279,123 @@ export async function createAndRegisterDefaultExecApprovalRequest(params: {
|
||||||
unavailableReason,
|
unavailableReason,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildDefaultExecApprovalRequestArgs(
|
||||||
|
params: DefaultExecApprovalRequestArgs,
|
||||||
|
): DefaultExecApprovalRequestArgs {
|
||||||
|
return {
|
||||||
|
warnings: params.warnings,
|
||||||
|
approvalRunningNoticeMs: params.approvalRunningNoticeMs,
|
||||||
|
createApprovalSlug: params.createApprovalSlug,
|
||||||
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildExecApprovalFollowupTarget(
|
||||||
|
params: ExecApprovalFollowupTarget,
|
||||||
|
): ExecApprovalFollowupTarget {
|
||||||
|
return {
|
||||||
|
approvalId: params.approvalId,
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
turnSourceChannel: params.turnSourceChannel,
|
||||||
|
turnSourceTo: params.turnSourceTo,
|
||||||
|
turnSourceAccountId: params.turnSourceAccountId,
|
||||||
|
turnSourceThreadId: params.turnSourceThreadId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createExecApprovalDecisionState(params: {
|
||||||
|
decision: string | null | undefined;
|
||||||
|
askFallback: ResolvedExecApprovals["agent"]["askFallback"];
|
||||||
|
obfuscationDetected: boolean;
|
||||||
|
}) {
|
||||||
|
const baseDecision = resolveBaseExecApprovalDecision({
|
||||||
|
decision: params.decision ?? null,
|
||||||
|
askFallback: params.askFallback,
|
||||||
|
obfuscationDetected: params.obfuscationDetected,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
baseDecision,
|
||||||
|
approvedByAsk: baseDecision.approvedByAsk,
|
||||||
|
deniedReason: baseDecision.deniedReason,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendExecApprovalFollowupResult(
|
||||||
|
target: ExecApprovalFollowupTarget,
|
||||||
|
resultText: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await sendExecApprovalFollowup({
|
||||||
|
approvalId: target.approvalId,
|
||||||
|
sessionKey: target.sessionKey,
|
||||||
|
turnSourceChannel: target.turnSourceChannel,
|
||||||
|
turnSourceTo: target.turnSourceTo,
|
||||||
|
turnSourceAccountId: target.turnSourceAccountId,
|
||||||
|
turnSourceThreadId: target.turnSourceThreadId,
|
||||||
|
resultText,
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildExecApprovalPendingToolResult(params: {
|
||||||
|
host: "gateway" | "node";
|
||||||
|
command: string;
|
||||||
|
cwd: string;
|
||||||
|
warningText: string;
|
||||||
|
approvalId: string;
|
||||||
|
approvalSlug: string;
|
||||||
|
expiresAtMs: number;
|
||||||
|
initiatingSurface: ExecApprovalInitiatingSurfaceState;
|
||||||
|
sentApproverDms: boolean;
|
||||||
|
unavailableReason: ExecApprovalUnavailableReason | null;
|
||||||
|
nodeId?: string;
|
||||||
|
}): AgentToolResult<ExecToolDetails> {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text:
|
||||||
|
params.unavailableReason !== null
|
||||||
|
? (buildExecApprovalUnavailableReplyPayload({
|
||||||
|
warningText: params.warningText,
|
||||||
|
reason: params.unavailableReason,
|
||||||
|
channelLabel: params.initiatingSurface.channelLabel,
|
||||||
|
sentApproverDms: params.sentApproverDms,
|
||||||
|
}).text ?? "")
|
||||||
|
: buildApprovalPendingMessage({
|
||||||
|
warningText: params.warningText,
|
||||||
|
approvalSlug: params.approvalSlug,
|
||||||
|
approvalId: params.approvalId,
|
||||||
|
command: params.command,
|
||||||
|
cwd: params.cwd,
|
||||||
|
host: params.host,
|
||||||
|
nodeId: params.nodeId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
details:
|
||||||
|
params.unavailableReason !== null
|
||||||
|
? ({
|
||||||
|
status: "approval-unavailable",
|
||||||
|
reason: params.unavailableReason,
|
||||||
|
channelLabel: params.initiatingSurface.channelLabel,
|
||||||
|
sentApproverDms: params.sentApproverDms,
|
||||||
|
host: params.host,
|
||||||
|
command: params.command,
|
||||||
|
cwd: params.cwd,
|
||||||
|
nodeId: params.nodeId,
|
||||||
|
warningText: params.warningText,
|
||||||
|
} satisfies ExecToolDetails)
|
||||||
|
: ({
|
||||||
|
status: "approval-pending",
|
||||||
|
approvalId: params.approvalId,
|
||||||
|
approvalSlug: params.approvalSlug,
|
||||||
|
expiresAtMs: params.expiresAtMs,
|
||||||
|
host: params.host,
|
||||||
|
command: params.command,
|
||||||
|
cwd: params.cwd,
|
||||||
|
nodeId: params.nodeId,
|
||||||
|
warningText: params.warningText,
|
||||||
|
} satisfies ExecToolDetails),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue