agentgate 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ import { Router } from 'express';
2
+ import {
3
+ getMessagingMode,
4
+ createAgentMessage,
5
+ getAgentMessage,
6
+ getMessagesForAgent,
7
+ markMessageRead,
8
+ listApiKeys,
9
+ getApiKeyByName
10
+ } from '../lib/db.js';
11
+ import { notifyAgentMessage } from '../lib/agentNotifier.js';
12
+
13
+ const router = Router();
14
+
15
+ const MAX_MESSAGE_LENGTH = 10 * 1024; // 10KB limit
16
+
17
+ // POST /api/agents/message - Send a message to another agent
18
+ router.post('/message', async (req, res) => {
19
+ const { to, message } = req.body;
20
+ const fromAgent = req.apiKeyName; // Set by apiKeyAuth middleware
21
+
22
+ if (!to) {
23
+ return res.status(400).json({ error: 'Missing "to" field (recipient agent name)' });
24
+ }
25
+
26
+ if (!message) {
27
+ return res.status(400).json({ error: 'Missing "message" field' });
28
+ }
29
+
30
+ // Check message length
31
+ if (message.length > MAX_MESSAGE_LENGTH) {
32
+ return res.status(400).json({
33
+ error: `Message too long. Maximum ${MAX_MESSAGE_LENGTH} bytes allowed.`,
34
+ length: message.length,
35
+ max: MAX_MESSAGE_LENGTH
36
+ });
37
+ }
38
+
39
+ const mode = getMessagingMode();
40
+
41
+ if (mode === 'off') {
42
+ return res.status(403).json({
43
+ error: 'Agent messaging is disabled',
44
+ hint: 'Admin can enable messaging in the agentgate UI'
45
+ });
46
+ }
47
+
48
+ // Validate recipient exists (case-insensitive lookup)
49
+ const recipient = getApiKeyByName(to);
50
+ if (!recipient) {
51
+ return res.status(404).json({ error: `Agent "${to}" not found` });
52
+ }
53
+
54
+ // Use canonical name from database
55
+ const recipientName = recipient.name;
56
+
57
+ // Can't message yourself (case-insensitive)
58
+ if (recipientName.toLowerCase() === fromAgent.toLowerCase()) {
59
+ return res.status(400).json({ error: 'Cannot send message to yourself' });
60
+ }
61
+
62
+ try {
63
+ // Use canonical recipient name from database
64
+ const result = createAgentMessage(fromAgent, recipientName, message);
65
+
66
+ if (mode === 'supervised') {
67
+ return res.json({
68
+ id: result.id,
69
+ status: 'pending',
70
+ message: 'Message queued for human approval'
71
+ });
72
+ } else {
73
+ // open mode - notify recipient immediately
74
+ const fullMessage = getAgentMessage(result.id);
75
+ notifyAgentMessage(fullMessage);
76
+
77
+ return res.json({
78
+ id: result.id,
79
+ status: 'delivered',
80
+ message: 'Message delivered'
81
+ });
82
+ }
83
+ } catch (err) {
84
+ return res.status(500).json({ error: err.message });
85
+ }
86
+ });
87
+
88
+ // GET /api/agents/messages - Get messages for the current agent
89
+ router.get('/messages', async (req, res) => {
90
+ const agentName = req.apiKeyName;
91
+ const unreadOnly = req.query.unread === 'true';
92
+
93
+ const mode = getMessagingMode();
94
+
95
+ if (mode === 'off') {
96
+ return res.status(403).json({
97
+ error: 'Agent messaging is disabled',
98
+ hint: 'Admin can enable messaging in the agentgate UI'
99
+ });
100
+ }
101
+
102
+ const messages = getMessagesForAgent(agentName, unreadOnly);
103
+
104
+ return res.json({
105
+ mode,
106
+ messages: messages.map(m => ({
107
+ id: m.id,
108
+ from: m.from_agent,
109
+ message: m.message,
110
+ created_at: m.created_at,
111
+ read: m.read_at !== null
112
+ }))
113
+ });
114
+ });
115
+
116
+ // POST /api/agents/messages/:id/read - Mark a message as read
117
+ router.post('/messages/:id/read', async (req, res) => {
118
+ const { id } = req.params;
119
+ const agentName = req.apiKeyName;
120
+
121
+ const mode = getMessagingMode();
122
+
123
+ if (mode === 'off') {
124
+ return res.status(403).json({ error: 'Agent messaging is disabled' });
125
+ }
126
+
127
+ const result = markMessageRead(id, agentName);
128
+
129
+ if (result.changes === 0) {
130
+ return res.status(404).json({ error: 'Message not found or already read' });
131
+ }
132
+
133
+ return res.json({ success: true });
134
+ });
135
+
136
+ // GET /api/agents/status - Get messaging status and mode
137
+ router.get('/status', async (req, res) => {
138
+ const mode = getMessagingMode();
139
+ const agentName = req.apiKeyName;
140
+
141
+ if (mode === 'off') {
142
+ return res.json({
143
+ mode: 'off',
144
+ enabled: false,
145
+ message: 'Agent messaging is disabled'
146
+ });
147
+ }
148
+
149
+ const messages = getMessagesForAgent(agentName, true);
150
+
151
+ return res.json({
152
+ mode,
153
+ enabled: true,
154
+ unread_count: messages.length
155
+ });
156
+ });
157
+
158
+ // GET /api/agents/messageable - Discover which agents can be messaged
159
+ router.get('/messageable', async (req, res) => {
160
+ const mode = getMessagingMode();
161
+ const callerName = req.apiKeyName;
162
+
163
+ if (mode === 'off') {
164
+ return res.status(403).json({
165
+ error: 'Agent messaging is disabled',
166
+ agents: []
167
+ });
168
+ }
169
+
170
+ const apiKeys = listApiKeys();
171
+
172
+ // Return all agents except self
173
+ const agents = apiKeys
174
+ .filter(k => k.name.toLowerCase() !== callerName.toLowerCase())
175
+ .map(k => ({
176
+ name: k.name
177
+ }));
178
+
179
+ return res.json({
180
+ mode,
181
+ agents
182
+ });
183
+ });
184
+
185
+ export default router;