@0xmaxma/claude-gateway 1.1.9 → 1.2.0
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.
- package/README.md +61 -1
- package/dist/agent/builtin-commands.d.ts +10 -0
- package/dist/agent/builtin-commands.d.ts.map +1 -0
- package/dist/agent/builtin-commands.js +38 -0
- package/dist/agent/builtin-commands.js.map +1 -0
- package/dist/agent/runner.d.ts +7 -5
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +56 -30
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/workspace-loader.d.ts.map +1 -1
- package/dist/agent/workspace-loader.js +9 -4
- package/dist/agent/workspace-loader.js.map +1 -1
- package/dist/api/apps-router.d.ts +7 -0
- package/dist/api/apps-router.d.ts.map +1 -0
- package/dist/api/apps-router.js +242 -0
- package/dist/api/apps-router.js.map +1 -0
- package/dist/api/gateway-router.d.ts +17 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +171 -2
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +385 -4
- package/dist/api/router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts +76 -0
- package/dist/apps/agent-manager.d.ts.map +1 -0
- package/dist/apps/agent-manager.js +311 -0
- package/dist/apps/agent-manager.js.map +1 -0
- package/dist/apps/compose-generator.d.ts +108 -0
- package/dist/apps/compose-generator.d.ts.map +1 -0
- package/dist/apps/compose-generator.js +687 -0
- package/dist/apps/compose-generator.js.map +1 -0
- package/dist/apps/installer.d.ts +101 -0
- package/dist/apps/installer.d.ts.map +1 -0
- package/dist/apps/installer.js +898 -0
- package/dist/apps/installer.js.map +1 -0
- package/dist/apps/registry-client.d.ts +37 -0
- package/dist/apps/registry-client.d.ts.map +1 -0
- package/dist/apps/registry-client.js +106 -0
- package/dist/apps/registry-client.js.map +1 -0
- package/dist/apps/registry.d.ts +54 -0
- package/dist/apps/registry.d.ts.map +1 -0
- package/dist/apps/registry.js +182 -0
- package/dist/apps/registry.js.map +1 -0
- package/dist/apps/socket-server.d.ts +46 -0
- package/dist/apps/socket-server.d.ts.map +1 -0
- package/dist/apps/socket-server.js +297 -0
- package/dist/apps/socket-server.js.map +1 -0
- package/dist/config/watcher.d.ts +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +55 -2
- package/dist/config/watcher.js.map +1 -1
- package/dist/index.js +170 -3
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +47 -6
- package/dist/session/process.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/mcp/server.ts +2 -0
- package/mcp/tools/apps/client.ts +78 -0
- package/mcp/tools/apps/module.ts +211 -0
- package/mcp/tools/apps/skills/app-status/SKILL.md +48 -0
- package/mcp/tools/apps/skills/create-app-yaml/SKILL.md +94 -0
- package/mcp/tools/apps/skills/install-app/SKILL.md +102 -0
- package/mcp/tools/apps/skills/list-apps/SKILL.md +34 -0
- package/mcp/tools/telegram/receiver-server.ts +36 -2
- package/package.json +1 -1
package/dist/api/router.js
CHANGED
|
@@ -51,6 +51,11 @@ const MAX_MESSAGE_LENGTH = 10000;
|
|
|
51
51
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
52
52
|
const AGENT_ID_RE = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
53
53
|
const SAFE_FILENAME_RE = /^[a-zA-Z0-9._\-() ]+$/;
|
|
54
|
+
function maskToken(token) {
|
|
55
|
+
if (token.length <= 12)
|
|
56
|
+
return '•'.repeat(token.length);
|
|
57
|
+
return token.slice(0, 8) + '•••••' + token.slice(-4);
|
|
58
|
+
}
|
|
54
59
|
/** Detect MIME type from file magic bytes (first 12 bytes). */
|
|
55
60
|
function detectMimeFromMagic(header) {
|
|
56
61
|
if (header[0] === 0xFF && header[1] === 0xD8 && header[2] === 0xFF)
|
|
@@ -323,8 +328,8 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
323
328
|
const timeoutMs = typeof timeout_ms === 'number' && timeout_ms > 0 && timeout_ms <= 600000
|
|
324
329
|
? timeout_ms
|
|
325
330
|
: DEFAULT_TIMEOUT_MS;
|
|
326
|
-
//
|
|
327
|
-
if (message.trim()
|
|
331
|
+
// Built-in command dispatch — only intercept known commands, let everything else reach Claude
|
|
332
|
+
if (runner_1.AgentRunner.isApiBuiltinCommand(message.trim())) {
|
|
328
333
|
try {
|
|
329
334
|
const result = await runner.executeApiCommand(sessionId, chatIdStr, message.trim());
|
|
330
335
|
res.json({ command: message.trim(), session_id: sessionId, result });
|
|
@@ -455,6 +460,11 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
455
460
|
model: cfg.claude?.model ?? null,
|
|
456
461
|
allow_tools: cfg.allow_tools ?? false,
|
|
457
462
|
avatarUrl: cfg.avatar ? `/api/v1/agents/${id}/avatar` : null,
|
|
463
|
+
telegram_connected: !!cfg.telegram?.botToken,
|
|
464
|
+
discord_connected: !!cfg.discord?.botToken,
|
|
465
|
+
telegram_token_preview: cfg.telegram?.botToken ? maskToken(cfg.telegram.botToken) : null,
|
|
466
|
+
discord_token_preview: cfg.discord?.botToken ? maskToken(cfg.discord.botToken) : null,
|
|
467
|
+
telegram_dm_policy: cfg.telegram?.botToken ? readTelegramAccess(id).dmPolicy : null,
|
|
458
468
|
}));
|
|
459
469
|
res.json({ agents });
|
|
460
470
|
});
|
|
@@ -544,6 +554,19 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
544
554
|
}
|
|
545
555
|
return;
|
|
546
556
|
}
|
|
557
|
+
// Update in-memory agentConfigs immediately so GET /api/v1/agents returns the new agent
|
|
558
|
+
// without waiting for the file watcher (~500ms debounce).
|
|
559
|
+
agentConfigs.set(id, {
|
|
560
|
+
id,
|
|
561
|
+
description: description.trim(),
|
|
562
|
+
workspace: workspaceAbs,
|
|
563
|
+
env: path.join(workspaceAbs, '.env'),
|
|
564
|
+
claude: {
|
|
565
|
+
model: typeof model === 'string' && model.trim() ? model.trim() : 'claude-sonnet-4-6',
|
|
566
|
+
dangerouslySkipPermissions: false,
|
|
567
|
+
extraFlags: [],
|
|
568
|
+
},
|
|
569
|
+
});
|
|
547
570
|
// Config written successfully — now create workspace directory and stub files.
|
|
548
571
|
const stubFiles = {
|
|
549
572
|
'AGENTS.md': `# Agent: ${id}\n\n${description.trim()}\n`,
|
|
@@ -575,6 +598,40 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
575
598
|
? path.join(path.dirname(configPath), 'agents')
|
|
576
599
|
: path.join(os.homedir(), '.claude-gateway', 'agents');
|
|
577
600
|
}
|
|
601
|
+
function getTelegramStateDir(agentId) {
|
|
602
|
+
const agentsBase = getAgentsBaseDir();
|
|
603
|
+
const cfg = agentConfigs.get(agentId);
|
|
604
|
+
const workspace = cfg?.workspace
|
|
605
|
+
? (cfg.workspace.startsWith('~') ? path.join(os.homedir(), cfg.workspace.slice(1)) : cfg.workspace)
|
|
606
|
+
: path.join(agentsBase, agentId, 'workspace');
|
|
607
|
+
return path.join(workspace, '.telegram-state');
|
|
608
|
+
}
|
|
609
|
+
function readTelegramAccess(agentId) {
|
|
610
|
+
const accessFile = path.join(getTelegramStateDir(agentId), 'access.json');
|
|
611
|
+
try {
|
|
612
|
+
const raw = fs.readFileSync(accessFile, 'utf8');
|
|
613
|
+
const parsed = JSON.parse(raw);
|
|
614
|
+
return {
|
|
615
|
+
dmPolicy: parsed.dmPolicy ?? 'pairing',
|
|
616
|
+
allowFrom: parsed.allowFrom ?? [],
|
|
617
|
+
groups: parsed.groups ?? {},
|
|
618
|
+
pending: parsed.pending ?? {},
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
return { dmPolicy: 'pairing', allowFrom: [], groups: {}, pending: {} };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function writeTelegramAccess(agentId, access) {
|
|
626
|
+
const stateDir = getTelegramStateDir(agentId);
|
|
627
|
+
try {
|
|
628
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
629
|
+
fs.writeFileSync(path.join(stateDir, 'access.json'), JSON.stringify(access, null, 2));
|
|
630
|
+
}
|
|
631
|
+
catch (err) {
|
|
632
|
+
throw new Error(`Failed to write Telegram access config: ${err.message}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
578
635
|
/**
|
|
579
636
|
* POST /api/v1/agents/wizard/start
|
|
580
637
|
* Start wizard: call Claude to generate workspace files, return wizardId + preview.
|
|
@@ -799,6 +856,17 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
799
856
|
});
|
|
800
857
|
return;
|
|
801
858
|
}
|
|
859
|
+
// Update in-memory agentConfigs immediately so GET /api/v1/agents returns the new agent
|
|
860
|
+
// without waiting for the file watcher (~500ms debounce).
|
|
861
|
+
agentConfigs.set(agentId, {
|
|
862
|
+
id: agentId,
|
|
863
|
+
description: wizard.prompt.slice(0, 200).trim(),
|
|
864
|
+
workspace: workspaceDirAbs,
|
|
865
|
+
env: path.join(workspaceDirAbs, '.env'),
|
|
866
|
+
claude: { model: defaultModel, dangerouslySkipPermissions: false, extraFlags: [] },
|
|
867
|
+
...(wizard.signatureEmoji ? { signatureEmoji: wizard.signatureEmoji } : {}),
|
|
868
|
+
...(avatarFilename ? { avatar: avatarFilename } : {}),
|
|
869
|
+
});
|
|
802
870
|
wizard_state_1.wizardStore.update(wizardId, { step: 'confirmed' });
|
|
803
871
|
const avatarUrl = avatarFilename ? `/api/v1/agents/${agentId}/avatar` : null;
|
|
804
872
|
res.json({
|
|
@@ -951,6 +1019,12 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
951
1019
|
});
|
|
952
1020
|
}
|
|
953
1021
|
catch { /* non-fatal */ }
|
|
1022
|
+
// Hot-start the receiver so the agent responds immediately without a gateway restart
|
|
1023
|
+
const runner = agentRunners.get(wizard.agentId);
|
|
1024
|
+
if (runner) {
|
|
1025
|
+
runner.updateAgentConfig({ ...runner.getAgentConfig(), telegram: { botToken: wizard.botToken } });
|
|
1026
|
+
runner.startTelegramReceiver();
|
|
1027
|
+
}
|
|
954
1028
|
wizard_state_1.wizardStore.delete(wizardId);
|
|
955
1029
|
res.json({ success: true, agentId: wizard.agentId });
|
|
956
1030
|
});
|
|
@@ -1000,7 +1074,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1000
1074
|
return;
|
|
1001
1075
|
}
|
|
1002
1076
|
const body = req.body;
|
|
1003
|
-
const { description, model, allow_tools } = body;
|
|
1077
|
+
const { description, model, allow_tools, telegram_bot_token, discord_bot_token } = body;
|
|
1004
1078
|
if (description !== undefined && (typeof description !== 'string' || !description.trim())) {
|
|
1005
1079
|
res.status(400).json({ error: 'description must be a non-empty string' });
|
|
1006
1080
|
return;
|
|
@@ -1013,6 +1087,14 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1013
1087
|
res.status(400).json({ error: 'allow_tools must be a boolean' });
|
|
1014
1088
|
return;
|
|
1015
1089
|
}
|
|
1090
|
+
if (telegram_bot_token !== undefined && telegram_bot_token !== null && typeof telegram_bot_token !== 'string') {
|
|
1091
|
+
res.status(400).json({ error: 'telegram_bot_token must be a string or null' });
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (discord_bot_token !== undefined && discord_bot_token !== null && typeof discord_bot_token !== 'string') {
|
|
1095
|
+
res.status(400).json({ error: 'discord_bot_token must be a string or null' });
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1016
1098
|
try {
|
|
1017
1099
|
await writeAgentsToConfig(configPath, (agents) => {
|
|
1018
1100
|
const agent = agents.find((a) => a.id === agentId);
|
|
@@ -1027,6 +1109,23 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1027
1109
|
}
|
|
1028
1110
|
if (allow_tools !== undefined)
|
|
1029
1111
|
agent.allow_tools = allow_tools;
|
|
1112
|
+
if (telegram_bot_token !== undefined) {
|
|
1113
|
+
if (telegram_bot_token === null || telegram_bot_token === '') {
|
|
1114
|
+
delete agent.telegram;
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
agent.telegram = { botToken: telegram_bot_token.trim() };
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (discord_bot_token !== undefined) {
|
|
1121
|
+
if (discord_bot_token === null || discord_bot_token === '') {
|
|
1122
|
+
delete agent.discord;
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
const existing = agent.discord;
|
|
1126
|
+
agent.discord = { ...(existing ?? {}), botToken: discord_bot_token.trim() };
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1030
1129
|
});
|
|
1031
1130
|
}
|
|
1032
1131
|
catch (err) {
|
|
@@ -1041,7 +1140,289 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1041
1140
|
cfg.claude.model = model.trim();
|
|
1042
1141
|
if (allow_tools !== undefined)
|
|
1043
1142
|
cfg.allow_tools = allow_tools;
|
|
1044
|
-
|
|
1143
|
+
if (telegram_bot_token !== undefined) {
|
|
1144
|
+
const token = typeof telegram_bot_token === 'string' ? telegram_bot_token.trim() : null;
|
|
1145
|
+
if (token) {
|
|
1146
|
+
cfg.telegram = { botToken: token };
|
|
1147
|
+
// Hot-start receiver if not already running
|
|
1148
|
+
const runner = agentRunners.get(agentId);
|
|
1149
|
+
if (runner) {
|
|
1150
|
+
runner.updateAgentConfig(cfg);
|
|
1151
|
+
runner.startTelegramReceiver();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
delete cfg.telegram;
|
|
1156
|
+
agentRunners.get(agentId)?.stopTelegramReceiver();
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (discord_bot_token !== undefined) {
|
|
1160
|
+
const token = typeof discord_bot_token === 'string' ? discord_bot_token.trim() : null;
|
|
1161
|
+
if (token) {
|
|
1162
|
+
cfg.discord = { ...(cfg.discord ?? {}), botToken: token };
|
|
1163
|
+
// Hot-start receiver if not already running
|
|
1164
|
+
const runner = agentRunners.get(agentId);
|
|
1165
|
+
if (runner) {
|
|
1166
|
+
runner.updateAgentConfig(cfg);
|
|
1167
|
+
runner.startDiscordReceiver();
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
delete cfg.discord;
|
|
1172
|
+
agentRunners.get(agentId)?.stopDiscordReceiver();
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
res.json({
|
|
1176
|
+
agent: {
|
|
1177
|
+
id: agentId,
|
|
1178
|
+
description: cfg.description,
|
|
1179
|
+
model: cfg.claude?.model,
|
|
1180
|
+
allow_tools: cfg.allow_tools ?? false,
|
|
1181
|
+
telegram_connected: !!cfg.telegram?.botToken,
|
|
1182
|
+
discord_connected: !!cfg.discord?.botToken,
|
|
1183
|
+
telegram_token_preview: cfg.telegram?.botToken ? maskToken(cfg.telegram.botToken) : null,
|
|
1184
|
+
discord_token_preview: cfg.discord?.botToken ? maskToken(cfg.discord.botToken) : null,
|
|
1185
|
+
telegram_dm_policy: cfg.telegram?.botToken ? readTelegramAccess(agentId).dmPolicy : null,
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
/**
|
|
1190
|
+
* GET /api/v1/agents/:agentId/telegram/pending
|
|
1191
|
+
* List pending Telegram pairing requests (non-expired).
|
|
1192
|
+
*/
|
|
1193
|
+
router.get('/v1/agents/:agentId/telegram/pending', auth, (req, res) => {
|
|
1194
|
+
const { agentId } = req.params;
|
|
1195
|
+
const apiKey = req.apiKey;
|
|
1196
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1197
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (!agentConfigs.has(agentId)) {
|
|
1201
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const access = readTelegramAccess(agentId);
|
|
1205
|
+
const now = Date.now();
|
|
1206
|
+
const expired = Object.keys(access.pending).filter((code) => access.pending[code].expiresAt <= now);
|
|
1207
|
+
if (expired.length > 0) {
|
|
1208
|
+
expired.forEach((code) => { delete access.pending[code]; });
|
|
1209
|
+
try {
|
|
1210
|
+
writeTelegramAccess(agentId, access);
|
|
1211
|
+
}
|
|
1212
|
+
catch { /* non-fatal cleanup */ }
|
|
1213
|
+
}
|
|
1214
|
+
const pending = Object.entries(access.pending)
|
|
1215
|
+
.map(([code, p]) => ({ code, senderId: p.senderId, chatId: p.chatId, createdAt: p.createdAt, expiresAt: p.expiresAt }));
|
|
1216
|
+
res.json({ pending });
|
|
1217
|
+
});
|
|
1218
|
+
/**
|
|
1219
|
+
* POST /api/v1/agents/:agentId/telegram/approve
|
|
1220
|
+
* Approve a pending Telegram pairing by code.
|
|
1221
|
+
*/
|
|
1222
|
+
router.post('/v1/agents/:agentId/telegram/approve', auth, (req, res) => {
|
|
1223
|
+
const { agentId } = req.params;
|
|
1224
|
+
const apiKey = req.apiKey;
|
|
1225
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1226
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (!agentConfigs.has(agentId)) {
|
|
1230
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const { code } = req.body;
|
|
1234
|
+
if (!code) {
|
|
1235
|
+
res.status(400).json({ error: 'code required' });
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const access = readTelegramAccess(agentId);
|
|
1239
|
+
const entry = access.pending[code];
|
|
1240
|
+
if (!entry || entry.expiresAt < Date.now()) {
|
|
1241
|
+
res.status(404).json({ error: 'Pairing code not found or expired' });
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (!access.allowFrom.includes(entry.senderId))
|
|
1245
|
+
access.allowFrom.push(entry.senderId);
|
|
1246
|
+
delete access.pending[code];
|
|
1247
|
+
try {
|
|
1248
|
+
writeTelegramAccess(agentId, access);
|
|
1249
|
+
const approvedDir = path.join(getTelegramStateDir(agentId), 'approved');
|
|
1250
|
+
fs.mkdirSync(approvedDir, { recursive: true });
|
|
1251
|
+
fs.writeFileSync(path.join(approvedDir, entry.senderId), entry.chatId);
|
|
1252
|
+
}
|
|
1253
|
+
catch (err) {
|
|
1254
|
+
res.status(500).json({ error: `Failed to approve pairing: ${err.message}` });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
res.json({ ok: true, senderId: entry.senderId });
|
|
1258
|
+
});
|
|
1259
|
+
/**
|
|
1260
|
+
* POST /api/v1/agents/:agentId/telegram/deny
|
|
1261
|
+
* Deny and remove a pending Telegram pairing by code.
|
|
1262
|
+
*/
|
|
1263
|
+
router.post('/v1/agents/:agentId/telegram/deny', auth, (req, res) => {
|
|
1264
|
+
const { agentId } = req.params;
|
|
1265
|
+
const apiKey = req.apiKey;
|
|
1266
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1267
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (!agentConfigs.has(agentId)) {
|
|
1271
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const { code } = req.body;
|
|
1275
|
+
if (!code) {
|
|
1276
|
+
res.status(400).json({ error: 'code required' });
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const access = readTelegramAccess(agentId);
|
|
1280
|
+
if (!access.pending[code]) {
|
|
1281
|
+
res.status(404).json({ error: 'Pairing code not found' });
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
delete access.pending[code];
|
|
1285
|
+
try {
|
|
1286
|
+
writeTelegramAccess(agentId, access);
|
|
1287
|
+
}
|
|
1288
|
+
catch (err) {
|
|
1289
|
+
res.status(500).json({ error: `Failed to deny pairing: ${err.message}` });
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
res.json({ ok: true });
|
|
1293
|
+
});
|
|
1294
|
+
/**
|
|
1295
|
+
* POST /api/v1/agents/:agentId/telegram/init-pairing
|
|
1296
|
+
* Write sentinel file so the next private message auto-approves sender as owner.
|
|
1297
|
+
*/
|
|
1298
|
+
router.post('/v1/agents/:agentId/telegram/init-pairing', auth, (req, res) => {
|
|
1299
|
+
const { agentId } = req.params;
|
|
1300
|
+
const apiKey = req.apiKey;
|
|
1301
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1302
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (!agentConfigs.has(agentId)) {
|
|
1306
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const stateDir = getTelegramStateDir(agentId);
|
|
1310
|
+
try {
|
|
1311
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
1312
|
+
fs.writeFileSync(path.join(stateDir, 'awaiting-owner'), '');
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
res.status(500).json({ error: `Failed to write sentinel: ${err.message}` });
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
res.json({ ok: true });
|
|
1319
|
+
});
|
|
1320
|
+
/**
|
|
1321
|
+
* GET /api/v1/agents/:agentId/telegram/pairing-status
|
|
1322
|
+
* Returns whether init-pairing sentinel is still active.
|
|
1323
|
+
*/
|
|
1324
|
+
router.get('/v1/agents/:agentId/telegram/pairing-status', auth, (req, res) => {
|
|
1325
|
+
const { agentId } = req.params;
|
|
1326
|
+
const apiKey = req.apiKey;
|
|
1327
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1328
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (!agentConfigs.has(agentId)) {
|
|
1332
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
const sentinelPath = path.join(getTelegramStateDir(agentId), 'awaiting-owner');
|
|
1336
|
+
let waiting = false;
|
|
1337
|
+
try {
|
|
1338
|
+
const stat = fs.statSync(sentinelPath);
|
|
1339
|
+
waiting = Date.now() - stat.mtimeMs < 10 * 60 * 1000;
|
|
1340
|
+
if (!waiting)
|
|
1341
|
+
fs.rmSync(sentinelPath, { force: true });
|
|
1342
|
+
}
|
|
1343
|
+
catch { /* ENOENT — not waiting */ }
|
|
1344
|
+
const access = readTelegramAccess(agentId);
|
|
1345
|
+
res.json({ waiting, allowFrom: access.allowFrom });
|
|
1346
|
+
});
|
|
1347
|
+
/**
|
|
1348
|
+
* PATCH /api/v1/agents/:agentId/telegram/policy
|
|
1349
|
+
* Update the Telegram DM policy for an agent.
|
|
1350
|
+
*/
|
|
1351
|
+
router.patch('/v1/agents/:agentId/telegram/policy', auth, (req, res) => {
|
|
1352
|
+
const { agentId } = req.params;
|
|
1353
|
+
const apiKey = req.apiKey;
|
|
1354
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1355
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (!agentConfigs.has(agentId)) {
|
|
1359
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const { dmPolicy } = req.body;
|
|
1363
|
+
const valid = ['open', 'pairing', 'allowlist', 'disabled'];
|
|
1364
|
+
if (!dmPolicy || !valid.includes(dmPolicy)) {
|
|
1365
|
+
res.status(400).json({ error: `dmPolicy must be one of: ${valid.join(', ')}` });
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const access = readTelegramAccess(agentId);
|
|
1369
|
+
access.dmPolicy = dmPolicy;
|
|
1370
|
+
try {
|
|
1371
|
+
writeTelegramAccess(agentId, access);
|
|
1372
|
+
}
|
|
1373
|
+
catch (err) {
|
|
1374
|
+
res.status(500).json({ error: `Failed to update policy: ${err.message}` });
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
res.json({ ok: true, dmPolicy });
|
|
1378
|
+
});
|
|
1379
|
+
/**
|
|
1380
|
+
* GET /api/v1/agents/:agentId/telegram/allowlist
|
|
1381
|
+
* Return all users in allowFrom for an agent's Telegram channel.
|
|
1382
|
+
*/
|
|
1383
|
+
router.get('/v1/agents/:agentId/telegram/allowlist', auth, (req, res) => {
|
|
1384
|
+
const { agentId } = req.params;
|
|
1385
|
+
const apiKey = req.apiKey;
|
|
1386
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1387
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!agentConfigs.has(agentId)) {
|
|
1391
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const access = readTelegramAccess(agentId);
|
|
1395
|
+
res.json({ allowFrom: access.allowFrom });
|
|
1396
|
+
});
|
|
1397
|
+
/**
|
|
1398
|
+
* DELETE /api/v1/agents/:agentId/telegram/allow/:userId
|
|
1399
|
+
* Remove a user from the allowFrom list. Admin only.
|
|
1400
|
+
*/
|
|
1401
|
+
router.delete('/v1/agents/:agentId/telegram/allow/:userId', auth, (req, res) => {
|
|
1402
|
+
const apiKey = req.apiKey;
|
|
1403
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1404
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const { agentId, userId } = req.params;
|
|
1408
|
+
if (!/^\d+$/.test(userId)) {
|
|
1409
|
+
res.status(400).json({ error: 'Invalid userId: must be a numeric Telegram user ID' });
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (!agentConfigs.has(agentId)) {
|
|
1413
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const access = readTelegramAccess(agentId);
|
|
1417
|
+
access.allowFrom = access.allowFrom.filter((id) => id !== userId);
|
|
1418
|
+
try {
|
|
1419
|
+
writeTelegramAccess(agentId, access);
|
|
1420
|
+
}
|
|
1421
|
+
catch (err) {
|
|
1422
|
+
res.status(500).json({ error: `Failed to update allowlist: ${err.message}` });
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
res.json({ ok: true });
|
|
1045
1426
|
});
|
|
1046
1427
|
/**
|
|
1047
1428
|
* DELETE /api/v1/agents/:agentId
|