fix(commands): preserve approval reviewer custody

This commit is contained in:
Dallin Romney
2026-08-18 01:00:20 -07:00
parent eb07eecd40
commit e074147ddd
3 changed files with 13 additions and 3 deletions
@@ -48,6 +48,7 @@ type ExecCall = {
type ExecDefaults = {
accountId?: string;
approvalReviewerDeviceId?: string;
approvalFollowup?: () => Promise<string | undefined>;
approvalFollowupMode?: string;
approvalFollowupText?: string;
@@ -302,7 +303,9 @@ afterEach(() => {
describe("diagnostics command", () => {
it("requests Gateway diagnostics approval without a duplicate pending chat reply", async () => {
const { execCalls, handleDiagnosticsCommand } = createDiagnosticsHandlerForTest();
const result = await handleDiagnosticsCommand(buildDiagnosticsParams("/diagnostics"), true);
const params = buildDiagnosticsParams("/diagnostics");
params.ctx.ApprovalReviewerDeviceId = "device-diagnostics-reviewer";
const result = await handleDiagnosticsCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply).toBeUndefined();
@@ -312,6 +315,7 @@ describe("diagnostics command", () => {
expect(execCall.defaults.security).toBe("allowlist");
expect(execCall.defaults.ask).toBe("always");
expect(execCall.defaults.trigger).toBe("diagnostics");
expect(execCall.defaults.approvalReviewerDeviceId).toBe("device-diagnostics-reviewer");
expect(execCall.defaults.approvalFollowupMode).toBe("direct");
expect(execCall.defaults.approvalWarningText).toContain(
"Diagnostics can include sensitive local logs and host-level runtime metadata.",
@@ -131,6 +131,7 @@ describe("buildExportTrajectoryCommandReply", () => {
it("requests per-run exec approval for trajectory exports", async () => {
const { execCalls, deps } = createExecDeps();
const params = makeParams();
params.ctx.ApprovalReviewerDeviceId = "device-trajectory-reviewer";
const reply = await buildExportTrajectoryCommandReply(params, deps);
@@ -151,6 +152,7 @@ describe("buildExportTrajectoryCommandReply", () => {
expect(execCall.defaults.sessionStore).toBe("/tmp/openclaw-sessions.json");
expect(execCall.defaults.currentChannelId).toBe("bot");
expect(execCall.defaults.accountId).toBe("account-1");
expect(execCall.defaults.approvalReviewerDeviceId).toBe("device-trajectory-reviewer");
expect(execCall.params.security).toBe("allowlist");
expect(execCall.params.ask).toBe("always");
expect(execCall.params.background).toBe(true);
@@ -133,8 +133,8 @@ export function readCommandDeliveryTarget(params: HandleCommandsParams): string
/**
* Resolves where an exec approval prompt for a command should be delivered:
* the private owner-DM target when one was resolved, else the originating
* command surface. Keeps the fallback ternaries in one place so private and
* origin routing cannot drift between command handlers.
* command surface. The originating reviewer device stays separate from a
* private delivery target so command handlers cannot drop approval custody.
*/
export function resolveCommandExecApprovalRoute(params: {
commandParams: HandleCommandsParams;
@@ -144,6 +144,7 @@ export function resolveCommandExecApprovalRoute(params: {
currentChannelId: string | undefined;
currentThreadTs: string | undefined;
accountId: string | undefined;
approvalReviewerDeviceId: string | undefined;
} {
const target = params.privateApprovalTarget;
return {
@@ -157,6 +158,9 @@ export function resolveCommandExecApprovalRoute(params: {
accountId: target
? (target.accountId ?? undefined)
: (params.commandParams.ctx.AccountId ?? undefined),
approvalReviewerDeviceId: normalizeOptionalString(
params.commandParams.ctx.ApprovalReviewerDeviceId,
),
};
}