feat(cli): add remaining resources, fix descriptions from code review
New read-only resources: - destinations (agent-to-agent ACL + routing map) - user-dms (DM channel cache) - dropped-messages (audit trail for dropped messages) - approvals (in-flight approval cards) Description fixes from reading source: - messaging-groups: add denied_at column (router checks it) - sessions: fix container_status (idle is unused, stopped is auto-restarted by sweep) - wirings: add note that threaded adapters force per-thread Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
36
src/cli/resources/approvals.ts
Normal file
36
src/cli/resources/approvals.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { registerResource } from '../crud.js';
|
||||
|
||||
registerResource({
|
||||
name: 'approval',
|
||||
plural: 'approvals',
|
||||
table: 'pending_approvals',
|
||||
description:
|
||||
'Pending approval — in-flight approval cards waiting for an admin response. Created by requestApproval() (self-mod install_packages/add_mcp_server) and OneCLI credential approval flow. Rows are deleted after the admin approves/rejects or the request expires.',
|
||||
idColumn: 'approval_id',
|
||||
columns: [
|
||||
{ name: 'approval_id', type: 'string', description: 'Unique approval identifier (also used as the card questionId).' },
|
||||
{ name: 'session_id', type: 'string', description: 'Session that requested the approval. Null for OneCLI credential approvals.' },
|
||||
{ name: 'request_id', type: 'string', description: 'Original request identifier (OneCLI request UUID or same as approval_id).' },
|
||||
{
|
||||
name: 'action',
|
||||
type: 'string',
|
||||
description: 'Action type — matches the registered approval handler (e.g. install_packages, add_mcp_server, onecli_credential).',
|
||||
},
|
||||
{ name: 'payload', type: 'json', description: 'JSON payload carried through to the approval handler.' },
|
||||
{ name: 'created_at', type: 'string', description: 'Auto-set.' },
|
||||
{ name: 'agent_group_id', type: 'string', description: 'Originating agent group.' },
|
||||
{ name: 'channel_type', type: 'string', description: 'Channel the approval card was delivered on.' },
|
||||
{ name: 'platform_id', type: 'string', description: 'Platform chat ID the card was delivered to.' },
|
||||
{ name: 'platform_message_id', type: 'string', description: 'Platform message ID of the delivered card (for editing on expiry).' },
|
||||
{ name: 'expires_at', type: 'string', description: 'When this approval expires (OneCLI gateway TTL).' },
|
||||
{
|
||||
name: 'status',
|
||||
type: 'string',
|
||||
description: 'Current status.',
|
||||
enum: ['pending', 'approved', 'rejected', 'expired'],
|
||||
},
|
||||
{ name: 'title', type: 'string', description: 'Card title shown to the admin.' },
|
||||
{ name: 'options_json', type: 'json', description: 'Card button options as JSON array.' },
|
||||
],
|
||||
operations: { list: 'open', get: 'open' },
|
||||
});
|
||||
77
src/cli/resources/destinations.ts
Normal file
77
src/cli/resources/destinations.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { getDb } from '../../db/connection.js';
|
||||
import { registerResource } from '../crud.js';
|
||||
|
||||
registerResource({
|
||||
name: 'destination',
|
||||
plural: 'destinations',
|
||||
table: 'agent_destinations',
|
||||
description:
|
||||
'Agent destination — per-agent routing entry and ACL. Each row authorizes an agent to send messages to a target (channel or another agent) and assigns a local name the agent uses to address it. Names are scoped to the source agent — two agents can have different local names for the same target. Created automatically when wiring channels or when agents create child agents.',
|
||||
idColumn: 'agent_group_id',
|
||||
columns: [
|
||||
{
|
||||
name: 'agent_group_id',
|
||||
type: 'string',
|
||||
description: 'The agent that owns this destination. References agent_groups.id.',
|
||||
},
|
||||
{
|
||||
name: 'local_name',
|
||||
type: 'string',
|
||||
description:
|
||||
'Name the agent uses to address this target (e.g. <message to="local_name">). Unique per agent. Lowercase, dash-separated.',
|
||||
},
|
||||
{
|
||||
name: 'target_type',
|
||||
type: 'string',
|
||||
description: '"channel" for messaging group targets, "agent" for agent-to-agent targets.',
|
||||
enum: ['channel', 'agent'],
|
||||
},
|
||||
{
|
||||
name: 'target_id',
|
||||
type: 'string',
|
||||
description: 'The target\'s ID — messaging_groups.id for channels, agent_groups.id for agents.',
|
||||
},
|
||||
{ name: 'created_at', type: 'string', description: 'Auto-set.' },
|
||||
],
|
||||
operations: { list: 'open' },
|
||||
customOperations: {
|
||||
add: {
|
||||
access: 'approval',
|
||||
description: 'Add a destination for an agent. Use --agent-group-id, --local-name, --target-type, --target-id.',
|
||||
handler: async (args) => {
|
||||
const agentGroupId = args.agent_group_id as string;
|
||||
const localName = args.local_name as string;
|
||||
const targetType = args.target_type as string;
|
||||
const targetId = args.target_id as string;
|
||||
if (!agentGroupId) throw new Error('--agent-group-id is required');
|
||||
if (!localName) throw new Error('--local-name is required');
|
||||
if (!targetType || !['channel', 'agent'].includes(targetType)) {
|
||||
throw new Error('--target-type must be channel or agent');
|
||||
}
|
||||
if (!targetId) throw new Error('--target-id is required');
|
||||
getDb()
|
||||
.prepare(
|
||||
`INSERT INTO agent_destinations (agent_group_id, local_name, target_type, target_id, created_at)
|
||||
VALUES (?, ?, ?, ?, datetime('now'))`,
|
||||
)
|
||||
.run(agentGroupId, localName, targetType, targetId);
|
||||
return { agent_group_id: agentGroupId, local_name: localName, target_type: targetType, target_id: targetId };
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
access: 'approval',
|
||||
description: 'Remove a destination from an agent. Use --agent-group-id and --local-name.',
|
||||
handler: async (args) => {
|
||||
const agentGroupId = args.agent_group_id as string;
|
||||
const localName = args.local_name as string;
|
||||
if (!agentGroupId) throw new Error('--agent-group-id is required');
|
||||
if (!localName) throw new Error('--local-name is required');
|
||||
const result = getDb()
|
||||
.prepare('DELETE FROM agent_destinations WHERE agent_group_id = ? AND local_name = ?')
|
||||
.run(agentGroupId, localName);
|
||||
if (result.changes === 0) throw new Error('destination not found');
|
||||
return { removed: { agent_group_id: agentGroupId, local_name: localName } };
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
28
src/cli/resources/dropped-messages.ts
Normal file
28
src/cli/resources/dropped-messages.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { registerResource } from '../crud.js';
|
||||
|
||||
registerResource({
|
||||
name: 'dropped-message',
|
||||
plural: 'dropped-messages',
|
||||
table: 'unregistered_senders',
|
||||
description:
|
||||
'Dropped message log — tracks messages that were dropped by the router or access gate. Aggregates by (channel_type, platform_id) with a running count. Reasons include: no_agent_wired (no wiring exists), no_agent_engaged (wiring exists but engage rules didn\'t fire), unknown_sender_strict (sender not recognized, strict policy), unknown_sender_request_approval (sender not recognized, approval requested).',
|
||||
idColumn: 'channel_type',
|
||||
columns: [
|
||||
{ name: 'channel_type', type: 'string', description: 'Channel adapter type of the dropped message.' },
|
||||
{ name: 'platform_id', type: 'string', description: 'Platform chat ID where the message was dropped.' },
|
||||
{ name: 'user_id', type: 'string', description: 'Sender user ID if resolved, null otherwise.' },
|
||||
{ name: 'sender_name', type: 'string', description: 'Sender display name if available.' },
|
||||
{
|
||||
name: 'reason',
|
||||
type: 'string',
|
||||
description: 'Why the message was dropped.',
|
||||
enum: ['no_agent_wired', 'no_agent_engaged', 'unknown_sender_strict', 'unknown_sender_request_approval'],
|
||||
},
|
||||
{ name: 'messaging_group_id', type: 'string', description: 'Messaging group ID if resolved.' },
|
||||
{ name: 'agent_group_id', type: 'string', description: 'Target agent group ID if resolved.' },
|
||||
{ name: 'message_count', type: 'number', description: 'Number of dropped messages from this sender on this chat.' },
|
||||
{ name: 'first_seen', type: 'string', description: 'First drop timestamp.' },
|
||||
{ name: 'last_seen', type: 'string', description: 'Most recent drop timestamp.' },
|
||||
],
|
||||
operations: { list: 'open' },
|
||||
});
|
||||
@@ -8,4 +8,8 @@ import './wirings.js';
|
||||
import './users.js';
|
||||
import './roles.js';
|
||||
import './members.js';
|
||||
import './destinations.js';
|
||||
import './user-dms.js';
|
||||
import './dropped-messages.js';
|
||||
import './approvals.js';
|
||||
import './sessions.js';
|
||||
|
||||
@@ -12,7 +12,8 @@ registerResource({
|
||||
{
|
||||
name: 'channel_type',
|
||||
type: 'string',
|
||||
description: 'Channel adapter type — matches the adapter registered by /add-<channel> (e.g. telegram, discord, slack, whatsapp).',
|
||||
description:
|
||||
'Channel adapter type — matches the adapter registered by /add-<channel> (e.g. telegram, discord, slack, whatsapp).',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@@ -44,6 +45,13 @@ registerResource({
|
||||
default: 'strict',
|
||||
updatable: true,
|
||||
},
|
||||
{
|
||||
name: 'denied_at',
|
||||
type: 'string',
|
||||
description:
|
||||
'Set when the owner explicitly denies registering this channel. While set, the router drops all messages silently without re-escalating. Cleared by any explicit wiring mutation.',
|
||||
updatable: true,
|
||||
},
|
||||
{ name: 'created_at', type: 'string', description: 'Auto-set.', generated: true },
|
||||
],
|
||||
operations: { list: 'open', get: 'open', create: 'approval', update: 'approval', delete: 'approval' },
|
||||
|
||||
@@ -34,7 +34,8 @@ registerResource({
|
||||
{
|
||||
name: 'container_status',
|
||||
type: 'string',
|
||||
description: '"running" — container alive. "idle" — exited, restarts on next message. "stopped" — needs explicit wake.',
|
||||
description:
|
||||
'"running" — container alive and polling. "stopped" — container exited; the sweep will restart it automatically when due messages arrive. "idle" — reserved, currently unused.',
|
||||
enum: ['running', 'idle', 'stopped'],
|
||||
},
|
||||
{ name: 'last_active', type: 'string', description: 'Last message or heartbeat. Used for stale detection.' },
|
||||
|
||||
21
src/cli/resources/user-dms.ts
Normal file
21
src/cli/resources/user-dms.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { registerResource } from '../crud.js';
|
||||
|
||||
registerResource({
|
||||
name: 'user-dm',
|
||||
plural: 'user-dms',
|
||||
table: 'user_dms',
|
||||
description:
|
||||
'User DM cache — maps (user, channel_type) to the messaging group used for DM delivery. Populated lazily by ensureUserDm() when the host needs to cold-DM a user (approvals, pairing). For direct-addressable channels (Telegram, WhatsApp) the handle IS the DM chat ID. For resolution-required channels (Discord, Slack) the adapter\'s openDM resolves it.',
|
||||
idColumn: 'user_id',
|
||||
columns: [
|
||||
{ name: 'user_id', type: 'string', description: 'User this DM route is for.' },
|
||||
{ name: 'channel_type', type: 'string', description: 'Channel adapter type.' },
|
||||
{
|
||||
name: 'messaging_group_id',
|
||||
type: 'string',
|
||||
description: 'The messaging group used to deliver DMs to this user on this channel.',
|
||||
},
|
||||
{ name: 'resolved_at', type: 'string', description: 'When this DM route was last resolved.' },
|
||||
],
|
||||
operations: { list: 'open' },
|
||||
});
|
||||
@@ -40,7 +40,8 @@ registerResource({
|
||||
{
|
||||
name: 'sender_scope',
|
||||
type: 'string',
|
||||
description: '"all" — any sender (subject to unknown_sender_policy). "known" — only users with a role or membership in this agent group.',
|
||||
description:
|
||||
'"all" — any sender (subject to unknown_sender_policy). "known" — only users with a role or membership in this agent group.',
|
||||
enum: ['all', 'known'],
|
||||
default: 'all',
|
||||
updatable: true,
|
||||
@@ -58,7 +59,7 @@ registerResource({
|
||||
name: 'session_mode',
|
||||
type: 'string',
|
||||
description:
|
||||
'"shared" — one session per (agent, messaging group). "per-thread" — separate session per thread/topic. "agent-shared" — one session across all messaging groups wired to this agent.',
|
||||
'"shared" — one session per (agent, messaging group). "per-thread" — separate session per thread/topic. "agent-shared" — one session across all messaging groups wired to this agent. Note: threaded adapters in group chats force per-thread regardless of this setting.',
|
||||
enum: ['shared', 'per-thread', 'agent-shared'],
|
||||
default: 'shared',
|
||||
updatable: true,
|
||||
|
||||
Reference in New Issue
Block a user