mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-12 18:11:51 +08:00
feat(v2/approvals): bundle install_packages + rebuild into one approval
Install approval now auto-rebuilds the image and kills the container, replacing the prior two-card flow where the agent had to call request_rebuild separately after install_packages was approved. Queues a processAfter=+5s synthetic prompt so the respawned container verifies the new packages and reports back to the user. Adds two v2-checklist gaps found along the way: - /remote-control and /remote-control-end are v1 host-level commands not ported to v2 - messaging_groups.admin_user_id is hardcoded null at registration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,8 @@ Status: [x] done, [~] partial, [ ] not started
|
||||
- [x] Admin user ID per group
|
||||
- [x] Admin-only command filtering in container
|
||||
- [ ] Admin model refactor — instance-level default admin (user + messaging app) for all approval routing, overridable per agent group; deliver approval cards to admin's DM when the platform supports it
|
||||
- [ ] `/remote-control` and `/remote-control-end` are v1 host-level commands not ported to v2 — listed in `ADMIN_COMMANDS` (formatter.ts:13) but unhandled in poll-loop, so they fall through to the Claude SDK which replies "Unknown skill". Found when `/remote-control` failed in a Telegram DM after the admin check passed.
|
||||
- [ ] `messaging_groups.admin_user_id` is hardcoded `null` at registration (`setup/register.ts:175`), so privileged commands like `/remote-control` always deny access until the column is manually backfilled in SQLite. Found when `/remote-control` failed in a freshly-registered Telegram DM.
|
||||
- [ ] Self-approval UX: when the user requesting a sensitive action IS the recorded admin/owner of the agent group, the agent still posts an Approve button back to that same person and narrates "waiting on admin approval" — confusing, redundant, and the "waiting" line stays visible even after the user immediately clicks approve.
|
||||
- [ ] Replace the `is_admin`/main vs. non-main distinction on agent groups with an explicit owner/admin model — every agent group has a recorded owner (the platform user who created or installed it) and an admin (who receives approvals and can change wiring); the two default to the same identity but can diverge (e.g. handoff). Drops the "main group = admin group" coupling. Downstream consequence: non-main group registration on Telegram must use the same pairing-code flow as main (`setup --step pair-telegram` with a `wire-to:<folder>` or `new-agent:<folder>` intent), since there's no longer a privileged "main" chat whose identity is trusted transitively — every group binds its own admin at registration time.
|
||||
- [x] Approval flow (sensitive action -> card to admin -> approve/reject -> execute) — `pending_approvals` table, `requestApproval()` helper, reuses interactive card infra
|
||||
|
||||
+1
-1
@@ -633,7 +633,7 @@ async function handleSystemAction(
|
||||
agentGroup.name,
|
||||
'install_packages',
|
||||
{ apt, npm, reason },
|
||||
`Agent "${agentGroup.name}" requests package installation:\n${packageList}${reason ? `\nReason: ${reason}` : ''}`,
|
||||
`Agent "${agentGroup.name}" requests package install + container rebuild:\n${packageList}${reason ? `\nReason: ${reason}` : ''}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
+24
-1
@@ -281,8 +281,31 @@ async function handleApprovalResponse(
|
||||
updateAgentGroup(session.agent_group_id, { container_config: JSON.stringify(containerConfig) });
|
||||
|
||||
const pkgs = [...(payload.apt || []), ...(payload.npm || [])].join(', ');
|
||||
notify(`Packages approved (${pkgs}). Call request_rebuild to apply them.`);
|
||||
log.info('Package install approved', { approvalId: approval.approval_id, userId });
|
||||
try {
|
||||
await buildAgentGroupImage(session.agent_group_id);
|
||||
killContainer(session.id, 'rebuild applied');
|
||||
// Schedule a follow-up prompt a few seconds after kill so the host sweep
|
||||
// respawns the container on the new image and the agent verifies + reports.
|
||||
writeSessionMessage(session.agent_group_id, session.id, {
|
||||
id: `appr-note-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
kind: 'chat',
|
||||
timestamp: new Date().toISOString(),
|
||||
platformId: session.agent_group_id,
|
||||
channelType: 'agent',
|
||||
threadId: null,
|
||||
content: JSON.stringify({
|
||||
text: `Packages installed (${pkgs}) and container rebuilt. Verify the new packages are available (e.g. run them or check versions) and report the result to the user.`,
|
||||
sender: 'system',
|
||||
senderId: 'system',
|
||||
}),
|
||||
processAfter: new Date(Date.now() + 5000).toISOString().replace('T', ' ').replace(/\.\d+Z$/, ''),
|
||||
});
|
||||
log.info('Container rebuild completed (bundled with install)', { approvalId: approval.approval_id });
|
||||
} catch (e) {
|
||||
notify(`Packages added to config (${pkgs}) but rebuild failed: ${e instanceof Error ? e.message : String(e)}. Call request_rebuild to retry.`);
|
||||
log.error('Bundled rebuild failed after install approval', { approvalId: approval.approval_id, err: e });
|
||||
}
|
||||
} else if (approval.action === 'request_rebuild') {
|
||||
try {
|
||||
await buildAgentGroupImage(session.agent_group_id);
|
||||
|
||||
Reference in New Issue
Block a user