mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-12 18:11:51 +08:00
feat(cli): wire approval flow for agent CLI commands
When a container agent calls an approval-gated ncl command, dispatch now sends an approval card to an admin instead of returning a stub error. On approve, the handler re-dispatches the original command and notifies the agent with the result. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+36
-3
@@ -6,6 +6,9 @@
|
||||
* Approval gating for risky calls from the container is the only branch
|
||||
* that differs by caller. Host callers and `open` commands run inline.
|
||||
*/
|
||||
import { getAgentGroup } from '../db/agent-groups.js';
|
||||
import { getSession } from '../db/sessions.js';
|
||||
import { registerApprovalHandler, requestApproval } from '../modules/approvals/index.js';
|
||||
import type { CallerContext, ErrorCode, RequestFrame, ResponseFrame } from './frame.js';
|
||||
import { lookup } from './registry.js';
|
||||
|
||||
@@ -15,10 +18,28 @@ export async function dispatch(req: RequestFrame, ctx: CallerContext): Promise<R
|
||||
return err(req.id, 'unknown-command', `no command "${req.command}"`);
|
||||
}
|
||||
|
||||
// Agent + approval-gated → approval flow. Wired alongside the first
|
||||
// approval-requiring command; until then, return a clear error.
|
||||
if (ctx.caller !== 'host' && cmd.access === 'approval') {
|
||||
return err(req.id, 'approval-pending', 'This command requires approval. (Approval flow not yet wired.)');
|
||||
const session = getSession(ctx.sessionId);
|
||||
if (!session) {
|
||||
return err(req.id, 'handler-error', 'Session not found.');
|
||||
}
|
||||
const agentGroup = getAgentGroup(ctx.agentGroupId);
|
||||
const agentName = agentGroup?.name ?? ctx.agentGroupId;
|
||||
|
||||
const argSummary = Object.entries(req.args)
|
||||
.map(([k, v]) => `--${k} ${v}`)
|
||||
.join(' ');
|
||||
|
||||
await requestApproval({
|
||||
session,
|
||||
agentName,
|
||||
action: 'cli_command',
|
||||
payload: { frame: { id: req.id, command: req.command, args: req.args } },
|
||||
title: `CLI: ${req.command}`,
|
||||
question: `Agent "${agentName}" wants to run:\n\`ncl ${req.command}${argSummary ? ' ' + argSummary : ''}\``,
|
||||
});
|
||||
|
||||
return err(req.id, 'approval-pending', 'Approval request sent to admin. You will be notified of the result.');
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
@@ -36,6 +57,18 @@ export async function dispatch(req: RequestFrame, ctx: CallerContext): Promise<R
|
||||
}
|
||||
}
|
||||
|
||||
registerApprovalHandler('cli_command', async ({ session, payload, userId, notify }) => {
|
||||
const frame = payload.frame as RequestFrame;
|
||||
const response = await dispatch(frame, { caller: 'host' });
|
||||
|
||||
if (response.ok) {
|
||||
const data = typeof response.data === 'string' ? response.data : JSON.stringify(response.data, null, 2);
|
||||
notify(`Your \`ncl ${frame.command}\` request was approved and executed.\n\n${data}`);
|
||||
} else {
|
||||
notify(`Your \`ncl ${frame.command}\` request was approved but failed: ${response.error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
function err(id: string, code: ErrorCode, message: string): ResponseFrame {
|
||||
return { id, ok: false, error: { code, message } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user