camelagi 0.5.39 → 0.5.45
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 +159 -27
- package/dashboard/404.html +1 -0
- package/dashboard/__next.__PAGE__.txt +9 -0
- package/dashboard/__next._full.txt +19 -0
- package/dashboard/__next._head.txt +6 -0
- package/dashboard/__next._index.txt +5 -0
- package/dashboard/__next._tree.txt +3 -0
- package/dashboard/_next/_next/static/T-oVqD_b6KFHCZwRKsnRE/_buildManifest.js +11 -0
- package/dashboard/_next/_next/static/T-oVqD_b6KFHCZwRKsnRE/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/_next/static/T-oVqD_b6KFHCZwRKsnRE/_ssgManifest.js +1 -0
- package/dashboard/_next/_next/static/chunks/0.9wqq91a2h.e.js +3 -0
- package/dashboard/_next/_next/static/chunks/00-nlsz4ud071.js +11 -0
- package/dashboard/_next/_next/static/chunks/00noe7du3vxk9.js +1 -0
- package/dashboard/_next/_next/static/chunks/03~yq9q893hmn.js +1 -0
- package/dashboard/_next/_next/static/chunks/04wr2uisy8870.js +5 -0
- package/dashboard/_next/_next/static/chunks/05f.w0h0j~.3g.js +1 -0
- package/dashboard/_next/_next/static/chunks/0geaa8~wrpcj6.js +1 -0
- package/dashboard/_next/_next/static/chunks/0nrlwe_~wde44.js +1 -0
- package/dashboard/_next/_next/static/chunks/12l48lp-434~..js +31 -0
- package/dashboard/_next/_next/static/chunks/14qcm-bqojrb9.js +1 -0
- package/dashboard/_next/_next/static/chunks/15~vsexhgsteo.js +1 -0
- package/dashboard/_next/_next/static/chunks/turbopack-173kubfog5kol.js +1 -0
- package/dashboard/_next/static/8Qf-y5Z4kUHAaWZgy3BFF/_buildManifest.js +11 -0
- package/dashboard/_next/static/8Qf-y5Z4kUHAaWZgy3BFF/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/8Qf-y5Z4kUHAaWZgy3BFF/_ssgManifest.js +1 -0
- package/dashboard/_next/static/IQ8CU45QQ3REnyUqiqjou/_buildManifest.js +11 -0
- package/dashboard/_next/static/IQ8CU45QQ3REnyUqiqjou/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/IQ8CU45QQ3REnyUqiqjou/_ssgManifest.js +1 -0
- package/dashboard/_next/static/PXgzS7j3kuwIb14Ph3vgL/_buildManifest.js +11 -0
- package/dashboard/_next/static/PXgzS7j3kuwIb14Ph3vgL/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/PXgzS7j3kuwIb14Ph3vgL/_ssgManifest.js +1 -0
- package/dashboard/_next/static/S6BdJ1Ro25a6twuts7gHi/_buildManifest.js +11 -0
- package/dashboard/_next/static/S6BdJ1Ro25a6twuts7gHi/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/S6BdJ1Ro25a6twuts7gHi/_ssgManifest.js +1 -0
- package/dashboard/_next/static/T-oVqD_b6KFHCZwRKsnRE/_buildManifest.js +11 -0
- package/dashboard/_next/static/T-oVqD_b6KFHCZwRKsnRE/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/T-oVqD_b6KFHCZwRKsnRE/_ssgManifest.js +1 -0
- package/dashboard/_next/static/bvUynE5XeBkbLQynmgHa8/_buildManifest.js +11 -0
- package/dashboard/_next/static/bvUynE5XeBkbLQynmgHa8/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/bvUynE5XeBkbLQynmgHa8/_ssgManifest.js +1 -0
- package/dashboard/_next/static/chunks/0.9wqq91a2h.e.js +3 -0
- package/dashboard/_next/static/chunks/00-nlsz4ud071.js +11 -0
- package/dashboard/_next/static/chunks/00noe7du3vxk9.js +1 -0
- package/dashboard/_next/static/chunks/03~yq9q893hmn.js +1 -0
- package/dashboard/_next/static/chunks/04wr2uisy8870.js +5 -0
- package/dashboard/_next/static/chunks/05f.w0h0j~.3g.js +1 -0
- package/dashboard/_next/static/chunks/08ug0khjvhl6m.js +11 -0
- package/dashboard/_next/static/chunks/0d1s5w1cd08~7.js +11 -0
- package/dashboard/_next/static/chunks/0geaa8~wrpcj6.js +1 -0
- package/dashboard/_next/static/chunks/0lyajo4_yki3f.js +11 -0
- package/dashboard/_next/static/chunks/0nrlwe_~wde44.js +1 -0
- package/dashboard/_next/static/chunks/0pd15v2_1spxb.js +11 -0
- package/dashboard/_next/static/chunks/0vnhcfh7g1u6i.js +11 -0
- package/dashboard/_next/static/chunks/0ynuwcqa7w3o..css +2 -0
- package/dashboard/_next/static/chunks/11dfqgfbc8fk2.js +11 -0
- package/dashboard/_next/static/chunks/12l48lp-434~..js +31 -0
- package/dashboard/_next/static/chunks/14qcm-bqojrb9.js +1 -0
- package/dashboard/_next/static/chunks/15~vsexhgsteo.js +1 -0
- package/dashboard/_next/static/chunks/turbopack-173kubfog5kol.js +1 -0
- package/dashboard/_next/static/media/1bffadaabf893a1e-s.16ipb6fqu393i.woff2 +0 -0
- package/dashboard/_next/static/media/2bbe8d2671613f1f-s.067x_6k0k23tk.woff2 +0 -0
- package/dashboard/_next/static/media/2c55a0e60120577a-s.0bjc5tiuqdqro.woff2 +0 -0
- package/dashboard/_next/static/media/5476f68d60460930-s.0wxq9webf.ew4.woff2 +0 -0
- package/dashboard/_next/static/media/83afe278b6a6bb3c-s.p.0q-301v4kxxnr.woff2 +0 -0
- package/dashboard/_next/static/media/9c72aa0f40e4eef8-s.0m6w47a4e5dy9.woff2 +0 -0
- package/dashboard/_next/static/media/ad66f9afd8947f86-s.11u06r12fd6v_.woff2 +0 -0
- package/dashboard/_next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
- package/dashboard/_next/static/uQ6ZB7JlP8tE93mlzKtXM/_buildManifest.js +11 -0
- package/dashboard/_next/static/uQ6ZB7JlP8tE93mlzKtXM/_clientMiddlewareManifest.js +1 -0
- package/dashboard/_next/static/uQ6ZB7JlP8tE93mlzKtXM/_ssgManifest.js +1 -0
- package/dashboard/_not-found/__next._full.txt +16 -0
- package/dashboard/_not-found/__next._head.txt +6 -0
- package/dashboard/_not-found/__next._index.txt +5 -0
- package/dashboard/_not-found/__next._not-found.__PAGE__.txt +5 -0
- package/dashboard/_not-found/__next._not-found.txt +5 -0
- package/dashboard/_not-found/__next._tree.txt +2 -0
- package/dashboard/_not-found.html +1 -0
- package/dashboard/_not-found.txt +16 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/file.svg +1 -0
- package/dashboard/globe.svg +1 -0
- package/dashboard/index.html +1 -0
- package/dashboard/index.txt +19 -0
- package/dashboard/next.svg +1 -0
- package/dashboard/vercel.svg +1 -0
- package/dashboard/window.svg +1 -0
- package/dist/agent/agent-cursor.js +138 -0
- package/dist/agent/agent-cursor.js.map +1 -0
- package/dist/agent/agent-sdk.js +71 -57
- package/dist/agent/agent-sdk.js.map +1 -1
- package/dist/agent/types.js +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/agent.js +11 -7
- package/dist/agent.js.map +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli/cmd-config.js +1 -1
- package/dist/cli/cmd-config.js.map +1 -1
- package/dist/cli/cmd-dashboard.js +63 -0
- package/dist/cli/cmd-dashboard.js.map +1 -0
- package/dist/cli/cmd-pairing.js +1 -1
- package/dist/cli/cmd-pairing.js.map +1 -1
- package/dist/cli/cmd-projects.js +128 -0
- package/dist/cli/cmd-projects.js.map +1 -0
- package/dist/cli/cmd-team.js +587 -0
- package/dist/cli/cmd-team.js.map +1 -0
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/config.js +33 -48
- package/dist/core/config.js.map +1 -1
- package/dist/core/constants.js +2 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/extensions/bot-approval.js.map +1 -0
- package/dist/extensions/pairing.js.map +1 -0
- package/dist/extensions/team-coordinator.js +63 -0
- package/dist/extensions/team-coordinator.js.map +1 -0
- package/dist/gateway/csrf.js +1 -1
- package/dist/gateway/csrf.js.map +1 -1
- package/dist/gateway/routes.js +198 -95
- package/dist/gateway/routes.js.map +1 -1
- package/dist/gateway/ws-handler.js +200 -168
- package/dist/gateway/ws-handler.js.map +1 -1
- package/dist/gateway-entry.js +8 -2
- package/dist/gateway-entry.js.map +1 -1
- package/dist/model.js +5 -4
- package/dist/model.js.map +1 -1
- package/dist/runtime/orchestrate.js +19 -8
- package/dist/runtime/orchestrate.js.map +1 -1
- package/dist/serve.js +18 -3
- package/dist/serve.js.map +1 -1
- package/dist/session.js +14 -2
- package/dist/session.js.map +1 -1
- package/dist/setup.js +17 -2
- package/dist/setup.js.map +1 -1
- package/dist/team.js +203 -0
- package/dist/team.js.map +1 -0
- package/dist/telegram/admin-agents.js +498 -0
- package/dist/telegram/admin-agents.js.map +1 -0
- package/dist/telegram/admin-bot.js +13 -934
- package/dist/telegram/admin-bot.js.map +1 -1
- package/dist/telegram/admin-commands.js +403 -0
- package/dist/telegram/admin-commands.js.map +1 -0
- package/dist/telegram/agent-bot.js +45 -1338
- package/dist/telegram/agent-bot.js.map +1 -1
- package/dist/telegram/agent-claude-code.js +555 -0
- package/dist/telegram/agent-claude-code.js.map +1 -0
- package/dist/telegram/agent-commands.js +396 -0
- package/dist/telegram/agent-commands.js.map +1 -0
- package/dist/telegram/agent-context.js +136 -0
- package/dist/telegram/agent-context.js.map +1 -0
- package/dist/telegram/agent-messages.js +249 -0
- package/dist/telegram/agent-messages.js.map +1 -0
- package/dist/telegram/dir-browser.js +9 -0
- package/dist/telegram/dir-browser.js.map +1 -1
- package/dist/telegram/draft-stream.js +4 -2
- package/dist/telegram/draft-stream.js.map +1 -1
- package/dist/telegram/resolve.js +15 -28
- package/dist/telegram/resolve.js.map +1 -1
- package/dist/telegram/terminal.js +139 -32
- package/dist/telegram/terminal.js.map +1 -1
- package/dist/telegram/wizards.js +1 -1
- package/dist/telegram/wizards.js.map +1 -1
- package/dist/telegram.js +4 -3
- package/dist/telegram.js.map +1 -1
- package/dist/tools/admin.js +841 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/team.js +182 -0
- package/dist/tools/team.js.map +1 -0
- package/dist/tui/commands.js +15 -0
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/components/welcome.js +2 -1
- package/dist/tui/components/welcome.js.map +1 -1
- package/dist/tui/tui.js +13 -1
- package/dist/tui/tui.js.map +1 -1
- package/package.json +4 -1
- package/dist/cli/cmd-install.js +0 -80
- package/dist/cli/cmd-install.js.map +0 -1
- package/dist/telegram/bot-approval.js.map +0 -1
- package/dist/telegram/pairing.js.map +0 -1
- /package/dist/{telegram → extensions}/bot-approval.js +0 -0
- /package/dist/{telegram → extensions}/pairing.js +0 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
// Admin tools: AI-powered agent management (replaces wizard-based admin for LLM agents)
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { loadConfig, saveConfig } from "../core/config.js";
|
|
6
|
+
import { agentMemoryDir, seedAgentWorkspace } from "../workspace.js";
|
|
7
|
+
import { listSessions, deleteSession, loadMessages } from "../session.js";
|
|
8
|
+
import { listPendingRequests, approveRequest, denyRequest } from "../extensions/pairing.js";
|
|
9
|
+
import { aggregateAgentUsage, formatTokens, formatCost } from "../usage.js";
|
|
10
|
+
import { SOUL_TEMPLATES, nameToId, validateBotToken, PRESETS } from "../telegram/wizards.js";
|
|
11
|
+
export function createAdminTools(deps) {
|
|
12
|
+
return [
|
|
13
|
+
adminAgentsTool(deps),
|
|
14
|
+
adminConfigTool,
|
|
15
|
+
adminMcpTool,
|
|
16
|
+
adminSoulTool,
|
|
17
|
+
adminBotTool(deps),
|
|
18
|
+
adminSessionsTool,
|
|
19
|
+
adminUsageTool,
|
|
20
|
+
adminPairingTool,
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
// ─── admin_agents: list, create, update, delete ─────────────────────
|
|
24
|
+
function adminAgentsTool(deps) {
|
|
25
|
+
return {
|
|
26
|
+
name: "admin_agents",
|
|
27
|
+
description: `Manage agents — list, create, update, or delete.
|
|
28
|
+
|
|
29
|
+
Actions:
|
|
30
|
+
- list: Show all agents with status, model, config
|
|
31
|
+
- create: Create a new agent with name, mode, model, telegram token, soul template, etc.
|
|
32
|
+
- update: Change an agent's setting (model, thinking, effort, maxTurns, name, briefMode)
|
|
33
|
+
- delete: Remove an agent (stops bot, removes config, preserves workspace files)
|
|
34
|
+
|
|
35
|
+
Soul templates for create: coding, research, writing, general, custom`,
|
|
36
|
+
schema: z.object({
|
|
37
|
+
action: z.enum(["list", "create", "update", "delete"]).describe("Action to perform"),
|
|
38
|
+
agentId: z.string().nullable().optional().describe("Agent ID (for update/delete)"),
|
|
39
|
+
name: z.string().nullable().optional().describe("Agent name (for create)"),
|
|
40
|
+
mode: z.enum(["llm", "claude-code"]).nullable().optional().describe("Agent mode (for create, default: llm)"),
|
|
41
|
+
model: z.string().nullable().optional().describe("Model override (for create/update)"),
|
|
42
|
+
thinking: z.enum(["off", "low", "medium", "high"]).nullable().optional().describe("Thinking level (for create/update)"),
|
|
43
|
+
effort: z.enum(["low", "medium", "high", "max"]).nullable().optional().describe("Effort level (for create/update)"),
|
|
44
|
+
maxTurns: z.number().nullable().optional().describe("Max turns (for create/update)"),
|
|
45
|
+
telegramBotToken: z.string().nullable().optional().describe("Telegram bot token from BotFather (for create)"),
|
|
46
|
+
soulTemplate: z.enum(["coding", "research", "writing", "general", "custom"]).nullable().optional().describe("Personality template (for create)"),
|
|
47
|
+
customSoul: z.string().nullable().optional().describe("Custom SOUL.md content (when soulTemplate is 'custom')"),
|
|
48
|
+
briefMode: z.boolean().nullable().optional().describe("Brief mode toggle (for update, requires telegram)"),
|
|
49
|
+
field: z.string().nullable().optional().describe("Field name to update (for update — model, thinking, effort, maxTurns, name, briefMode)"),
|
|
50
|
+
value: z.string().nullable().optional().describe("New value for the field (for update)"),
|
|
51
|
+
}),
|
|
52
|
+
execute: async (args) => {
|
|
53
|
+
const { action } = args;
|
|
54
|
+
const config = loadConfig();
|
|
55
|
+
if (action === "list") {
|
|
56
|
+
return listAgents(config);
|
|
57
|
+
}
|
|
58
|
+
if (action === "create") {
|
|
59
|
+
return createAgent(config, args, deps);
|
|
60
|
+
}
|
|
61
|
+
if (action === "update") {
|
|
62
|
+
return updateAgent(config, args);
|
|
63
|
+
}
|
|
64
|
+
if (action === "delete") {
|
|
65
|
+
return deleteAgent(config, args);
|
|
66
|
+
}
|
|
67
|
+
return `Unknown action: ${action}`;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function listAgents(config) {
|
|
72
|
+
const entries = Object.entries(config.agents);
|
|
73
|
+
if (entries.length === 0)
|
|
74
|
+
return "No agents configured.";
|
|
75
|
+
let runningBots = [];
|
|
76
|
+
try {
|
|
77
|
+
const { getActiveBotIds } = await import("../telegram.js");
|
|
78
|
+
runningBots = getActiveBotIds();
|
|
79
|
+
}
|
|
80
|
+
catch { /* not in telegram context */ }
|
|
81
|
+
const lines = [];
|
|
82
|
+
for (const [id, a] of entries) {
|
|
83
|
+
const running = runningBots.includes(id);
|
|
84
|
+
const status = running ? "running" : a.telegram?.botToken ? "stopped" : "no bot";
|
|
85
|
+
const model = a.model ?? config.model;
|
|
86
|
+
const thinking = a.thinking ?? config.thinking;
|
|
87
|
+
const effort = a.effort ?? config.effort;
|
|
88
|
+
const maxTurns = a.maxTurns ?? config.maxTurns;
|
|
89
|
+
const mcpCount = a.mcp ? Object.keys(a.mcp.servers).length : 0;
|
|
90
|
+
const mode = a.mode ?? "llm";
|
|
91
|
+
lines.push(`[${id}] ${a.name}`);
|
|
92
|
+
lines.push(` Status: ${status} | Mode: ${mode} | Admin: ${a.admin ? "yes" : "no"}`);
|
|
93
|
+
lines.push(` Model: ${model} | Thinking: ${thinking} | Effort: ${effort} | Max Turns: ${maxTurns}`);
|
|
94
|
+
if (mcpCount > 0)
|
|
95
|
+
lines.push(` MCP: ${mcpCount} server(s)`);
|
|
96
|
+
if (a.telegram?.botToken)
|
|
97
|
+
lines.push(` Telegram: configured`);
|
|
98
|
+
if (a.discord?.botToken)
|
|
99
|
+
lines.push(` Discord: configured`);
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
async function createAgent(config, args, deps) {
|
|
105
|
+
const name = args.name;
|
|
106
|
+
if (!name)
|
|
107
|
+
return "Error: name is required for create.";
|
|
108
|
+
const existingIds = Object.keys(config.agents);
|
|
109
|
+
const id = nameToId(name, existingIds);
|
|
110
|
+
const mode = args.mode ?? "llm";
|
|
111
|
+
const model = args.model;
|
|
112
|
+
const thinking = args.thinking;
|
|
113
|
+
const effort = args.effort;
|
|
114
|
+
const maxTurns = args.maxTurns;
|
|
115
|
+
const telegramBotToken = args.telegramBotToken;
|
|
116
|
+
const soulTemplate = args.soulTemplate ?? "general";
|
|
117
|
+
const customSoul = args.customSoul;
|
|
118
|
+
// Validate telegram token if provided
|
|
119
|
+
let tokenInfo = "";
|
|
120
|
+
if (telegramBotToken) {
|
|
121
|
+
const result = await validateBotToken(telegramBotToken);
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
return `Telegram token validation failed: ${result.error}`;
|
|
124
|
+
}
|
|
125
|
+
tokenInfo = `\nTelegram: @${result.username}`;
|
|
126
|
+
}
|
|
127
|
+
// Seed workspace
|
|
128
|
+
const description = soulTemplate === "custom" ? customSoul : undefined;
|
|
129
|
+
seedAgentWorkspace(id, name, description);
|
|
130
|
+
// Apply soul template
|
|
131
|
+
const tmpl = SOUL_TEMPLATES[soulTemplate];
|
|
132
|
+
if (tmpl?.soul) {
|
|
133
|
+
const soulPath = path.join(agentMemoryDir(id), "SOUL.md");
|
|
134
|
+
fs.writeFileSync(soulPath, tmpl.soul);
|
|
135
|
+
}
|
|
136
|
+
else if (soulTemplate === "custom" && customSoul) {
|
|
137
|
+
const soulPath = path.join(agentMemoryDir(id), "SOUL.md");
|
|
138
|
+
fs.writeFileSync(soulPath, customSoul);
|
|
139
|
+
}
|
|
140
|
+
// Build agent config
|
|
141
|
+
const agentConfig = { name };
|
|
142
|
+
if (mode === "claude-code")
|
|
143
|
+
agentConfig.mode = "claude-code";
|
|
144
|
+
if (model)
|
|
145
|
+
agentConfig.model = model;
|
|
146
|
+
if (thinking)
|
|
147
|
+
agentConfig.thinking = thinking;
|
|
148
|
+
if (effort)
|
|
149
|
+
agentConfig.effort = effort;
|
|
150
|
+
if (maxTurns)
|
|
151
|
+
agentConfig.maxTurns = maxTurns;
|
|
152
|
+
if (telegramBotToken) {
|
|
153
|
+
agentConfig.telegram = {
|
|
154
|
+
botToken: telegramBotToken,
|
|
155
|
+
allowedUsers: config.agents.admin?.telegram?.allowedUsers ?? [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const agents = { ...config.agents, [id]: agentConfig };
|
|
159
|
+
saveConfig({ agents });
|
|
160
|
+
// Start bot if token provided
|
|
161
|
+
if (telegramBotToken) {
|
|
162
|
+
try {
|
|
163
|
+
const { startBot } = await import("../telegram.js");
|
|
164
|
+
await startBot(id, telegramBotToken, () => loadConfig(), deps.getSystemPrompt);
|
|
165
|
+
tokenInfo += " (started)";
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
169
|
+
tokenInfo += ` (bot not started: ${msg})`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const modeLabel = mode === "claude-code" ? "Claude Code" : `LLM — ${model ?? config.model}`;
|
|
173
|
+
return [
|
|
174
|
+
`Agent created!`,
|
|
175
|
+
``,
|
|
176
|
+
`Name: ${name}`,
|
|
177
|
+
`ID: ${id}`,
|
|
178
|
+
`Mode: ${modeLabel}`,
|
|
179
|
+
thinking ? `Thinking: ${thinking}` : null,
|
|
180
|
+
effort ? `Effort: ${effort}` : null,
|
|
181
|
+
maxTurns ? `Max Turns: ${maxTurns}` : null,
|
|
182
|
+
tokenInfo || null,
|
|
183
|
+
`Workspace: ${agentMemoryDir(id)}`,
|
|
184
|
+
].filter(Boolean).join("\n");
|
|
185
|
+
}
|
|
186
|
+
function updateAgent(config, args) {
|
|
187
|
+
const agentId = args.agentId;
|
|
188
|
+
if (!agentId)
|
|
189
|
+
return "Error: agentId is required for update.";
|
|
190
|
+
if (!config.agents[agentId])
|
|
191
|
+
return `Error: agent "${agentId}" not found.`;
|
|
192
|
+
const agents = { ...config.agents };
|
|
193
|
+
const agent = { ...agents[agentId] };
|
|
194
|
+
// Direct field updates from args
|
|
195
|
+
const directFields = ["model", "thinking", "effort", "maxTurns", "name"];
|
|
196
|
+
let updated = false;
|
|
197
|
+
const changes = [];
|
|
198
|
+
for (const f of directFields) {
|
|
199
|
+
if (args[f] !== undefined && args[f] !== null) {
|
|
200
|
+
agent[f] = args[f];
|
|
201
|
+
changes.push(`${f} = ${args[f]}`);
|
|
202
|
+
updated = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Brief mode (nested under telegram)
|
|
206
|
+
if (args.briefMode !== undefined && args.briefMode !== null) {
|
|
207
|
+
if (!agent.telegram)
|
|
208
|
+
return "Error: agent has no Telegram config — briefMode requires Telegram.";
|
|
209
|
+
agent.telegram = { ...agent.telegram, briefMode: args.briefMode };
|
|
210
|
+
changes.push(`briefMode = ${args.briefMode}`);
|
|
211
|
+
updated = true;
|
|
212
|
+
}
|
|
213
|
+
// Generic field/value update
|
|
214
|
+
if (!updated && args.field && args.value !== undefined) {
|
|
215
|
+
const field = args.field;
|
|
216
|
+
let value = args.value;
|
|
217
|
+
if (value === "true")
|
|
218
|
+
value = true;
|
|
219
|
+
else if (value === "false")
|
|
220
|
+
value = false;
|
|
221
|
+
else if (typeof value === "string" && /^\d+$/.test(value))
|
|
222
|
+
value = parseInt(value, 10);
|
|
223
|
+
if (field === "briefMode") {
|
|
224
|
+
if (!agent.telegram)
|
|
225
|
+
return "Error: agent has no Telegram config.";
|
|
226
|
+
agent.telegram = { ...agent.telegram, briefMode: value };
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
agent[field] = value;
|
|
230
|
+
}
|
|
231
|
+
changes.push(`${field} = ${value}`);
|
|
232
|
+
updated = true;
|
|
233
|
+
}
|
|
234
|
+
if (!updated)
|
|
235
|
+
return "Error: no changes specified. Provide field values or field+value params.";
|
|
236
|
+
agents[agentId] = agent;
|
|
237
|
+
saveConfig({ agents });
|
|
238
|
+
return `Updated ${agentId}:\n${changes.map(c => ` ${c}`).join("\n")}`;
|
|
239
|
+
}
|
|
240
|
+
async function deleteAgent(config, args) {
|
|
241
|
+
const agentId = args.agentId;
|
|
242
|
+
if (!agentId)
|
|
243
|
+
return "Error: agentId is required for delete.";
|
|
244
|
+
if (!config.agents[agentId])
|
|
245
|
+
return `Error: agent "${agentId}" not found.`;
|
|
246
|
+
if (config.agents[agentId].admin)
|
|
247
|
+
return `Error: cannot delete admin agent "${agentId}".`;
|
|
248
|
+
// Stop bot if running
|
|
249
|
+
try {
|
|
250
|
+
const { stopBot } = await import("../telegram.js");
|
|
251
|
+
stopBot(agentId);
|
|
252
|
+
}
|
|
253
|
+
catch { /* not in telegram context */ }
|
|
254
|
+
const agents = { ...config.agents };
|
|
255
|
+
delete agents[agentId];
|
|
256
|
+
saveConfig({ agents });
|
|
257
|
+
return `Agent "${agentId}" deleted. Workspace files preserved at ${agentMemoryDir(agentId)}`;
|
|
258
|
+
}
|
|
259
|
+
// ─── admin_config: get, set, setup_provider ─────────────────────────
|
|
260
|
+
const adminConfigTool = {
|
|
261
|
+
name: "admin_config",
|
|
262
|
+
description: `View or modify CamelAGI configuration.
|
|
263
|
+
|
|
264
|
+
Actions:
|
|
265
|
+
- get: View current config (full or specific key). API key is masked.
|
|
266
|
+
- set: Update a config value. Supports dot-notation for nested keys (e.g. "approvals.mode").
|
|
267
|
+
- setup_provider: Configure API provider, key, model, and base URL in one call.
|
|
268
|
+
|
|
269
|
+
Available providers for setup_provider: anthropic, openai, openrouter, ollama, custom`,
|
|
270
|
+
schema: z.object({
|
|
271
|
+
action: z.enum(["get", "set", "setup_provider"]).describe("Action to perform"),
|
|
272
|
+
key: z.string().nullable().optional().describe("Config key (for get/set, dot-separated for nested)"),
|
|
273
|
+
value: z.string().nullable().optional().describe("New value (for set)"),
|
|
274
|
+
provider: z.enum(["anthropic", "openai", "openrouter", "ollama", "custom"]).nullable().optional().describe("Provider (for setup_provider)"),
|
|
275
|
+
apiKey: z.string().nullable().optional().describe("API key (for setup_provider)"),
|
|
276
|
+
baseUrl: z.string().nullable().optional().describe("Base URL (for setup_provider with custom/openrouter)"),
|
|
277
|
+
model: z.string().nullable().optional().describe("Model name (for setup_provider)"),
|
|
278
|
+
}),
|
|
279
|
+
execute: async (args) => {
|
|
280
|
+
const { action } = args;
|
|
281
|
+
const config = loadConfig();
|
|
282
|
+
if (action === "get") {
|
|
283
|
+
const key = args.key;
|
|
284
|
+
if (!key) {
|
|
285
|
+
return [
|
|
286
|
+
`Provider: ${config.provider}`,
|
|
287
|
+
`Model: ${config.model}`,
|
|
288
|
+
config.baseUrl ? `Base URL: ${config.baseUrl}` : null,
|
|
289
|
+
`API Key: ${config.apiKey ? "***" + config.apiKey.slice(-4) : "not set"}`,
|
|
290
|
+
`Thinking: ${config.thinking}`,
|
|
291
|
+
`Effort: ${config.effort}`,
|
|
292
|
+
`Max Turns: ${config.maxTurns}`,
|
|
293
|
+
`Timeout: ${config.timeoutSeconds}s`,
|
|
294
|
+
`Approvals: ${config.approvals.mode}`,
|
|
295
|
+
`Compaction: ${config.compaction.enabled ? "on" : "off"} (max ${config.compaction.maxTokens} tokens)`,
|
|
296
|
+
`Voice: ${config.voice.enabled ? config.voice.provider : "disabled"}`,
|
|
297
|
+
`Agents: ${Object.keys(config.agents).length}`,
|
|
298
|
+
`MCP Servers: ${Object.keys(config.mcp.servers).length}`,
|
|
299
|
+
`Cron Jobs: ${config.cron.length}`,
|
|
300
|
+
].filter(Boolean).join("\n");
|
|
301
|
+
}
|
|
302
|
+
const parts = key.split(".");
|
|
303
|
+
let value = config;
|
|
304
|
+
for (const part of parts) {
|
|
305
|
+
if (value && typeof value === "object") {
|
|
306
|
+
value = value[part];
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
return `Key not found: ${key}`;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (key.includes("apiKey") || key.includes("botToken")) {
|
|
313
|
+
const str = String(value ?? "");
|
|
314
|
+
return `${key} = ${str ? "***" + str.slice(-4) : "not set"}`;
|
|
315
|
+
}
|
|
316
|
+
return `${key} = ${JSON.stringify(value, null, 2)}`;
|
|
317
|
+
}
|
|
318
|
+
if (action === "set") {
|
|
319
|
+
const key = args.key;
|
|
320
|
+
const rawValue = args.value;
|
|
321
|
+
if (!key || rawValue === undefined)
|
|
322
|
+
return "Error: key and value are required for set.";
|
|
323
|
+
let parsed = rawValue;
|
|
324
|
+
if (rawValue === "true")
|
|
325
|
+
parsed = true;
|
|
326
|
+
else if (rawValue === "false")
|
|
327
|
+
parsed = false;
|
|
328
|
+
else if (/^\d+$/.test(rawValue))
|
|
329
|
+
parsed = parseInt(rawValue, 10);
|
|
330
|
+
const parts = key.split(".");
|
|
331
|
+
let update;
|
|
332
|
+
if (parts.length === 1) {
|
|
333
|
+
update = { [key]: parsed };
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
const topKey = parts[0];
|
|
337
|
+
const subKey = parts.slice(1).join(".");
|
|
338
|
+
const existing = config[topKey];
|
|
339
|
+
if (typeof existing === "object" && existing !== null) {
|
|
340
|
+
update = { [topKey]: { ...existing, [subKey]: parsed } };
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
return `Unknown config section: ${topKey}`;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
saveConfig(update);
|
|
347
|
+
return `Config updated: ${key} = ${rawValue}`;
|
|
348
|
+
}
|
|
349
|
+
if (action === "setup_provider") {
|
|
350
|
+
const provider = args.provider;
|
|
351
|
+
if (!provider)
|
|
352
|
+
return "Error: provider is required for setup_provider.";
|
|
353
|
+
const preset = PRESETS[provider];
|
|
354
|
+
const values = {
|
|
355
|
+
provider: preset?.provider ?? "openai",
|
|
356
|
+
};
|
|
357
|
+
const apiKey = args.apiKey;
|
|
358
|
+
const baseUrl = args.baseUrl;
|
|
359
|
+
const model = args.model;
|
|
360
|
+
if (apiKey)
|
|
361
|
+
values.apiKey = apiKey;
|
|
362
|
+
if (model)
|
|
363
|
+
values.model = model;
|
|
364
|
+
else if (preset?.models.length)
|
|
365
|
+
values.model = preset.models[0];
|
|
366
|
+
if (provider === "custom" && baseUrl) {
|
|
367
|
+
values.baseUrl = baseUrl;
|
|
368
|
+
}
|
|
369
|
+
else if (preset?.baseUrl) {
|
|
370
|
+
values.baseUrl = preset.baseUrl;
|
|
371
|
+
}
|
|
372
|
+
saveConfig(values);
|
|
373
|
+
const maskedKey = apiKey ? `***${apiKey.slice(-4)}` : "not set";
|
|
374
|
+
return [
|
|
375
|
+
`Provider configured!`,
|
|
376
|
+
``,
|
|
377
|
+
`Provider: ${values.provider}`,
|
|
378
|
+
`Model: ${values.model}`,
|
|
379
|
+
values.baseUrl ? `Base URL: ${values.baseUrl}` : null,
|
|
380
|
+
`API Key: ${maskedKey}`,
|
|
381
|
+
].filter(Boolean).join("\n");
|
|
382
|
+
}
|
|
383
|
+
return `Unknown action: ${action}`;
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
// ─── admin_mcp: add, remove, list ───────────────────────────────────
|
|
387
|
+
const adminMcpTool = {
|
|
388
|
+
name: "admin_mcp",
|
|
389
|
+
description: `Manage MCP (Model Context Protocol) tool servers.
|
|
390
|
+
|
|
391
|
+
Actions:
|
|
392
|
+
- list: Show all MCP servers (global and per-agent)
|
|
393
|
+
- add: Add a new MCP server
|
|
394
|
+
- remove: Remove an MCP server
|
|
395
|
+
|
|
396
|
+
Transport types for add:
|
|
397
|
+
- stdio: Local command (e.g. "npx -y @modelcontextprotocol/server-github")
|
|
398
|
+
- http: Remote server via URL
|
|
399
|
+
- sse: Streaming server via URL
|
|
400
|
+
|
|
401
|
+
Servers can be global or agent-specific (set agentId for per-agent).`,
|
|
402
|
+
schema: z.object({
|
|
403
|
+
action: z.enum(["list", "add", "remove"]).describe("Action to perform"),
|
|
404
|
+
agentId: z.string().nullable().optional().describe("Agent ID for per-agent MCP (omit for global)"),
|
|
405
|
+
name: z.string().nullable().optional().describe("Server name (auto-derived if omitted for add)"),
|
|
406
|
+
transport: z.enum(["stdio", "http", "sse"]).nullable().optional().describe("Transport type (for add)"),
|
|
407
|
+
command: z.string().nullable().optional().describe("Full command for stdio (e.g. 'npx -y @modelcontextprotocol/server-github')"),
|
|
408
|
+
url: z.string().nullable().optional().describe("Server URL (for http/sse)"),
|
|
409
|
+
env: z.record(z.string(), z.string()).nullable().optional().describe("Environment variables (for stdio)"),
|
|
410
|
+
headers: z.record(z.string(), z.string()).nullable().optional().describe("HTTP headers (for http/sse)"),
|
|
411
|
+
authToken: z.string().nullable().optional().describe("Bearer auth token (for http/sse, added to headers)"),
|
|
412
|
+
}),
|
|
413
|
+
execute: async (args) => {
|
|
414
|
+
const { action } = args;
|
|
415
|
+
const config = loadConfig();
|
|
416
|
+
const agentId = args.agentId;
|
|
417
|
+
if (action === "list") {
|
|
418
|
+
const lines = [];
|
|
419
|
+
// Global servers
|
|
420
|
+
const globalEntries = Object.entries(config.mcp.servers);
|
|
421
|
+
if (globalEntries.length > 0) {
|
|
422
|
+
lines.push("Global MCP Servers:");
|
|
423
|
+
for (const [name, s] of globalEntries) {
|
|
424
|
+
const cfg = s;
|
|
425
|
+
if (cfg.type === "stdio") {
|
|
426
|
+
const cmdArgs = Array.isArray(cfg.args) ? cfg.args.join(" ") : "";
|
|
427
|
+
lines.push(` [${name}] stdio — ${cfg.command} ${cmdArgs}`.trimEnd());
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
lines.push(` [${name}] ${cfg.type} — ${cfg.url}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
lines.push("No global MCP servers.");
|
|
436
|
+
}
|
|
437
|
+
// Per-agent servers
|
|
438
|
+
for (const [id, a] of Object.entries(config.agents)) {
|
|
439
|
+
if (!a.mcp?.servers || Object.keys(a.mcp.servers).length === 0)
|
|
440
|
+
continue;
|
|
441
|
+
lines.push(`\n${a.name} (${id}) MCP Servers:`);
|
|
442
|
+
for (const [name, s] of Object.entries(a.mcp.servers)) {
|
|
443
|
+
const cfg = s;
|
|
444
|
+
if (cfg.type === "stdio") {
|
|
445
|
+
const cmdArgs = Array.isArray(cfg.args) ? cfg.args.join(" ") : "";
|
|
446
|
+
lines.push(` [${name}] stdio — ${cfg.command} ${cmdArgs}`.trimEnd());
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
lines.push(` [${name}] ${cfg.type} — ${cfg.url}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return lines.join("\n") || "No MCP servers configured.";
|
|
454
|
+
}
|
|
455
|
+
if (action === "add") {
|
|
456
|
+
const transport = args.transport;
|
|
457
|
+
if (!transport)
|
|
458
|
+
return "Error: transport is required (stdio, http, or sse).";
|
|
459
|
+
const currentServers = agentId && config.agents[agentId]
|
|
460
|
+
? config.agents[agentId]?.mcp?.servers ?? {}
|
|
461
|
+
: config.mcp.servers;
|
|
462
|
+
const existing = Object.keys(currentServers);
|
|
463
|
+
let serverName;
|
|
464
|
+
let serverConfig;
|
|
465
|
+
if (transport === "stdio") {
|
|
466
|
+
const command = args.command;
|
|
467
|
+
if (!command)
|
|
468
|
+
return "Error: command is required for stdio transport.";
|
|
469
|
+
const parts = command.trim().split(/\s+/);
|
|
470
|
+
serverName = args.name ?? deriveStdioName(command, existing);
|
|
471
|
+
serverConfig = {
|
|
472
|
+
type: "stdio",
|
|
473
|
+
command: parts[0],
|
|
474
|
+
args: parts.slice(1),
|
|
475
|
+
};
|
|
476
|
+
if (args.env)
|
|
477
|
+
serverConfig.env = args.env;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const url = args.url;
|
|
481
|
+
if (!url)
|
|
482
|
+
return "Error: url is required for http/sse transport.";
|
|
483
|
+
serverName = args.name ?? deriveUrlName(url, existing);
|
|
484
|
+
serverConfig = { type: transport, url };
|
|
485
|
+
const headers = {};
|
|
486
|
+
if (args.headers)
|
|
487
|
+
Object.assign(headers, args.headers);
|
|
488
|
+
if (args.authToken)
|
|
489
|
+
headers.Authorization = `Bearer ${args.authToken}`;
|
|
490
|
+
if (Object.keys(headers).length > 0)
|
|
491
|
+
serverConfig.headers = headers;
|
|
492
|
+
}
|
|
493
|
+
const updated = { ...currentServers, [serverName]: serverConfig };
|
|
494
|
+
if (agentId && config.agents[agentId]) {
|
|
495
|
+
const agents = { ...config.agents };
|
|
496
|
+
agents[agentId] = { ...agents[agentId], mcp: { servers: updated } };
|
|
497
|
+
saveConfig({ agents });
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
saveConfig({ mcp: { servers: updated } });
|
|
501
|
+
}
|
|
502
|
+
const scope = agentId ? config.agents[agentId]?.name ?? agentId : "global";
|
|
503
|
+
return `MCP server added (${scope}):\n Name: ${serverName}\n Type: ${transport}\n ${args.url ?? args.command}`;
|
|
504
|
+
}
|
|
505
|
+
if (action === "remove") {
|
|
506
|
+
const name = args.name;
|
|
507
|
+
if (!name)
|
|
508
|
+
return "Error: name is required for remove.";
|
|
509
|
+
if (agentId && config.agents[agentId]) {
|
|
510
|
+
const servers = { ...(config.agents[agentId]?.mcp?.servers ?? {}) };
|
|
511
|
+
if (!(name in servers))
|
|
512
|
+
return `Server "${name}" not found in agent "${agentId}".`;
|
|
513
|
+
delete servers[name];
|
|
514
|
+
const agents = { ...config.agents };
|
|
515
|
+
agents[agentId] = { ...agents[agentId], mcp: { servers } };
|
|
516
|
+
saveConfig({ agents });
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
const servers = { ...config.mcp.servers };
|
|
520
|
+
if (!(name in servers))
|
|
521
|
+
return `Global server "${name}" not found.`;
|
|
522
|
+
delete servers[name];
|
|
523
|
+
saveConfig({ mcp: { servers } });
|
|
524
|
+
}
|
|
525
|
+
return `Removed MCP server: ${name}`;
|
|
526
|
+
}
|
|
527
|
+
return `Unknown action: ${action}`;
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
function deriveStdioName(command, existing) {
|
|
531
|
+
const match = command.match(/(?:@[\w-]+\/)?([\w-]+)\s*$/);
|
|
532
|
+
let base = match?.[1] ?? "server";
|
|
533
|
+
base = base.replace(/^(mcp-server-|server-|mcp-)/, "").replace(/(-mcp|-server)$/, "");
|
|
534
|
+
if (!base)
|
|
535
|
+
base = "server";
|
|
536
|
+
let name = base;
|
|
537
|
+
let i = 2;
|
|
538
|
+
while (existing.includes(name)) {
|
|
539
|
+
name = `${base}${i++}`;
|
|
540
|
+
}
|
|
541
|
+
return name;
|
|
542
|
+
}
|
|
543
|
+
function deriveUrlName(url, existing) {
|
|
544
|
+
try {
|
|
545
|
+
const host = new URL(url).hostname;
|
|
546
|
+
let base = host.replace(/^(www|api|mcp)\./, "").replace(/\.(com|io|ai|dev|org|net)$/, "").replace(/\./g, "-");
|
|
547
|
+
if (!base)
|
|
548
|
+
base = "server";
|
|
549
|
+
let name = base;
|
|
550
|
+
let i = 2;
|
|
551
|
+
while (existing.includes(name)) {
|
|
552
|
+
name = `${base}${i++}`;
|
|
553
|
+
}
|
|
554
|
+
return name;
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return `server${existing.length + 1}`;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// ─── admin_soul: read, write ────────────────────────────────────────
|
|
561
|
+
const adminSoulTool = {
|
|
562
|
+
name: "admin_soul",
|
|
563
|
+
description: `Read or write an agent's SOUL.md personality file.
|
|
564
|
+
|
|
565
|
+
Actions:
|
|
566
|
+
- read: Get the current SOUL.md content for an agent
|
|
567
|
+
- write: Replace the SOUL.md content for an agent`,
|
|
568
|
+
schema: z.object({
|
|
569
|
+
action: z.enum(["read", "write"]).describe("Action to perform"),
|
|
570
|
+
agentId: z.string().describe("Agent ID"),
|
|
571
|
+
content: z.string().nullable().optional().describe("New SOUL.md content (for write)"),
|
|
572
|
+
}),
|
|
573
|
+
execute: async (args) => {
|
|
574
|
+
const { action, agentId } = args;
|
|
575
|
+
const config = loadConfig();
|
|
576
|
+
if (!config.agents[agentId])
|
|
577
|
+
return `Error: agent "${agentId}" not found.`;
|
|
578
|
+
const soulPath = path.join(agentMemoryDir(agentId), "SOUL.md");
|
|
579
|
+
if (action === "read") {
|
|
580
|
+
if (!fs.existsSync(soulPath))
|
|
581
|
+
return `No SOUL.md for "${agentId}" yet.`;
|
|
582
|
+
const content = fs.readFileSync(soulPath, "utf-8").trim();
|
|
583
|
+
return content || "(empty)";
|
|
584
|
+
}
|
|
585
|
+
if (action === "write") {
|
|
586
|
+
const content = args.content;
|
|
587
|
+
if (!content)
|
|
588
|
+
return "Error: content is required for write.";
|
|
589
|
+
const dir = agentMemoryDir(agentId);
|
|
590
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
591
|
+
fs.writeFileSync(soulPath, content);
|
|
592
|
+
return `SOUL.md updated for "${agentId}" (${content.length} chars).`;
|
|
593
|
+
}
|
|
594
|
+
return `Unknown action: ${action}`;
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
// ─── admin_bot: status, start, stop, restart ────────────────────────
|
|
598
|
+
function adminBotTool(deps) {
|
|
599
|
+
return {
|
|
600
|
+
name: "admin_bot",
|
|
601
|
+
description: `Manage Telegram bot lifecycle.
|
|
602
|
+
|
|
603
|
+
Actions:
|
|
604
|
+
- status: Show which bots are running/stopped
|
|
605
|
+
- start: Start a specific agent's bot
|
|
606
|
+
- stop: Stop a specific agent's bot
|
|
607
|
+
- restart: Stop + start (specific agent or all non-admin bots)`,
|
|
608
|
+
schema: z.object({
|
|
609
|
+
action: z.enum(["status", "start", "stop", "restart"]).describe("Action to perform"),
|
|
610
|
+
agentId: z.string().nullable().optional().describe("Agent ID (for start/stop, optional for restart = all)"),
|
|
611
|
+
}),
|
|
612
|
+
execute: async (args) => {
|
|
613
|
+
const { action } = args;
|
|
614
|
+
const agentId = args.agentId;
|
|
615
|
+
const config = loadConfig();
|
|
616
|
+
// Dynamic import to avoid circular dependency issues at module load time
|
|
617
|
+
const { getActiveBotIds, startBot, stopBot } = await import("../telegram.js");
|
|
618
|
+
if (action === "status") {
|
|
619
|
+
const running = getActiveBotIds();
|
|
620
|
+
const allIds = Object.keys(config.agents);
|
|
621
|
+
const lines = [];
|
|
622
|
+
for (const id of allIds) {
|
|
623
|
+
const a = config.agents[id];
|
|
624
|
+
const isRunning = running.includes(id);
|
|
625
|
+
const hasTelegram = !!a.telegram?.botToken;
|
|
626
|
+
const status = isRunning ? "running" : hasTelegram ? "stopped" : "no bot";
|
|
627
|
+
lines.push(`${id}: ${status}`);
|
|
628
|
+
}
|
|
629
|
+
return lines.length > 0 ? lines.join("\n") : "No agents configured.";
|
|
630
|
+
}
|
|
631
|
+
if (action === "start") {
|
|
632
|
+
if (!agentId)
|
|
633
|
+
return "Error: agentId is required for start.";
|
|
634
|
+
const agent = config.agents[agentId];
|
|
635
|
+
if (!agent)
|
|
636
|
+
return `Error: agent "${agentId}" not found.`;
|
|
637
|
+
if (!agent.telegram?.botToken)
|
|
638
|
+
return `Error: agent "${agentId}" has no Telegram bot token.`;
|
|
639
|
+
try {
|
|
640
|
+
await startBot(agentId, agent.telegram.botToken, () => loadConfig(), deps.getSystemPrompt);
|
|
641
|
+
return `Bot "${agentId}" started.`;
|
|
642
|
+
}
|
|
643
|
+
catch (err) {
|
|
644
|
+
return `Error starting bot: ${err instanceof Error ? err.message : String(err)}`;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (action === "stop") {
|
|
648
|
+
if (!agentId)
|
|
649
|
+
return "Error: agentId is required for stop.";
|
|
650
|
+
const stopped = stopBot(agentId);
|
|
651
|
+
return stopped ? `Bot "${agentId}" stopped.` : `Bot "${agentId}" was not running.`;
|
|
652
|
+
}
|
|
653
|
+
if (action === "restart") {
|
|
654
|
+
if (agentId) {
|
|
655
|
+
const agent = config.agents[agentId];
|
|
656
|
+
if (!agent)
|
|
657
|
+
return `Error: agent "${agentId}" not found.`;
|
|
658
|
+
if (!agent.telegram?.botToken)
|
|
659
|
+
return `Error: agent "${agentId}" has no Telegram bot token.`;
|
|
660
|
+
stopBot(agentId);
|
|
661
|
+
try {
|
|
662
|
+
await startBot(agentId, agent.telegram.botToken, () => loadConfig(), deps.getSystemPrompt);
|
|
663
|
+
return `Restarted ${agentId}`;
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
return `Error restarting: ${err instanceof Error ? err.message : String(err)}`;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
// Restart all non-admin bots
|
|
670
|
+
const restarted = [];
|
|
671
|
+
const errors = [];
|
|
672
|
+
for (const [id, a] of Object.entries(config.agents)) {
|
|
673
|
+
if (a.admin || !a.telegram?.botToken)
|
|
674
|
+
continue;
|
|
675
|
+
stopBot(id);
|
|
676
|
+
try {
|
|
677
|
+
await startBot(id, a.telegram.botToken, () => loadConfig(), deps.getSystemPrompt);
|
|
678
|
+
restarted.push(id);
|
|
679
|
+
}
|
|
680
|
+
catch (err) {
|
|
681
|
+
errors.push(`${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const lines = [];
|
|
685
|
+
if (restarted.length > 0)
|
|
686
|
+
lines.push(`Restarted: ${restarted.join(", ")}`);
|
|
687
|
+
if (errors.length > 0)
|
|
688
|
+
lines.push(`Errors:\n${errors.join("\n")}`);
|
|
689
|
+
return lines.join("\n") || "No bots to restart.";
|
|
690
|
+
}
|
|
691
|
+
return `Unknown action: ${action}`;
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
// ─── admin_sessions: list, clear ────────────────────────────────────
|
|
696
|
+
const adminSessionsTool = {
|
|
697
|
+
name: "admin_sessions",
|
|
698
|
+
description: `Manage chat sessions.
|
|
699
|
+
|
|
700
|
+
Actions:
|
|
701
|
+
- list: Show recent sessions (up to 20)
|
|
702
|
+
- clear: Delete sessions older than a given age (1d, 1w, 1m)`,
|
|
703
|
+
schema: z.object({
|
|
704
|
+
action: z.enum(["list", "clear"]).describe("Action to perform"),
|
|
705
|
+
olderThan: z.enum(["1d", "1w", "1m"]).nullable().optional().describe("Age threshold for clear (default: 1w)"),
|
|
706
|
+
}),
|
|
707
|
+
execute: async (args) => {
|
|
708
|
+
const { action } = args;
|
|
709
|
+
if (action === "list") {
|
|
710
|
+
const sessions = listSessions();
|
|
711
|
+
if (sessions.length === 0)
|
|
712
|
+
return "No sessions.";
|
|
713
|
+
const recent = sessions.slice(0, 20);
|
|
714
|
+
const lines = [`Sessions (${sessions.length} total):\n`];
|
|
715
|
+
for (const s of recent) {
|
|
716
|
+
const age = formatAge(s.createdAt);
|
|
717
|
+
const msgs = loadMessages(s.id).length;
|
|
718
|
+
const label = s.label ? ` — ${s.label}` : "";
|
|
719
|
+
lines.push(`${s.id}${label}`);
|
|
720
|
+
lines.push(` ${s.model} · ${msgs} msgs · ${age}`);
|
|
721
|
+
}
|
|
722
|
+
if (sessions.length > 20)
|
|
723
|
+
lines.push(`\n... and ${sessions.length - 20} more`);
|
|
724
|
+
return lines.join("\n");
|
|
725
|
+
}
|
|
726
|
+
if (action === "clear") {
|
|
727
|
+
const period = args.olderThan ?? "1w";
|
|
728
|
+
const cutoff = { "1d": 86400000, "1w": 604800000, "1m": 2592000000 }[period] ?? 604800000;
|
|
729
|
+
const now = Date.now();
|
|
730
|
+
const sessions = listSessions();
|
|
731
|
+
let deleted = 0;
|
|
732
|
+
for (const s of sessions) {
|
|
733
|
+
if (now - s.createdAt > cutoff) {
|
|
734
|
+
deleteSession(s.id);
|
|
735
|
+
deleted++;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return `Deleted ${deleted} session(s) older than ${period}.`;
|
|
739
|
+
}
|
|
740
|
+
return `Unknown action: ${action}`;
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
function formatAge(timestamp) {
|
|
744
|
+
const ms = Date.now() - timestamp;
|
|
745
|
+
const seconds = Math.floor(ms / 1000);
|
|
746
|
+
if (seconds < 60)
|
|
747
|
+
return `${seconds}s ago`;
|
|
748
|
+
const minutes = Math.floor(seconds / 60);
|
|
749
|
+
if (minutes < 60)
|
|
750
|
+
return `${minutes}m ago`;
|
|
751
|
+
const hours = Math.floor(minutes / 60);
|
|
752
|
+
if (hours < 24)
|
|
753
|
+
return `${hours}h ago`;
|
|
754
|
+
const days = Math.floor(hours / 24);
|
|
755
|
+
return `${days}d ago`;
|
|
756
|
+
}
|
|
757
|
+
// ─── admin_usage: per-agent usage summary ───────────────────────────
|
|
758
|
+
const adminUsageTool = {
|
|
759
|
+
name: "admin_usage",
|
|
760
|
+
description: "Show per-agent token usage and estimated costs.",
|
|
761
|
+
schema: z.object({}),
|
|
762
|
+
execute: async () => {
|
|
763
|
+
const config = loadConfig();
|
|
764
|
+
const entries = Object.entries(config.agents).filter(([, a]) => !a.admin);
|
|
765
|
+
if (entries.length === 0)
|
|
766
|
+
return "No agents configured.";
|
|
767
|
+
const lines = ["Usage Summary\n"];
|
|
768
|
+
let totalCost = 0;
|
|
769
|
+
let hasData = false;
|
|
770
|
+
for (const [id, a] of entries) {
|
|
771
|
+
const model = a.model ?? config.model;
|
|
772
|
+
const summary = aggregateAgentUsage(id, a.name, model);
|
|
773
|
+
const total = summary.totalInput + summary.totalOutput;
|
|
774
|
+
if (total === 0 && summary.calls === 0)
|
|
775
|
+
continue;
|
|
776
|
+
hasData = true;
|
|
777
|
+
lines.push(`${a.name} (${model})`);
|
|
778
|
+
lines.push(` ${formatTokens(summary.totalInput)} in | ${formatTokens(summary.totalOutput)} out | ${summary.calls} calls`);
|
|
779
|
+
if (summary.estimatedCost !== undefined) {
|
|
780
|
+
lines.push(` Cost: ~${formatCost(summary.estimatedCost)}`);
|
|
781
|
+
totalCost += summary.estimatedCost;
|
|
782
|
+
}
|
|
783
|
+
lines.push("");
|
|
784
|
+
}
|
|
785
|
+
if (!hasData)
|
|
786
|
+
return "No usage data yet.";
|
|
787
|
+
if (totalCost > 0)
|
|
788
|
+
lines.push(`Total estimated cost: ~${formatCost(totalCost)}`);
|
|
789
|
+
return lines.join("\n");
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
// ─── admin_pairing: list, approve, deny ─────────────────────────────
|
|
793
|
+
const adminPairingTool = {
|
|
794
|
+
name: "admin_pairing",
|
|
795
|
+
description: `Manage user access pairing requests.
|
|
796
|
+
|
|
797
|
+
Actions:
|
|
798
|
+
- list: Show all pending access requests
|
|
799
|
+
- approve: Approve a request by code (adds user to allowedUsers)
|
|
800
|
+
- deny: Deny a request by code`,
|
|
801
|
+
schema: z.object({
|
|
802
|
+
action: z.enum(["list", "approve", "deny"]).describe("Action to perform"),
|
|
803
|
+
code: z.string().nullable().optional().describe("Pairing code (for approve/deny)"),
|
|
804
|
+
}),
|
|
805
|
+
execute: async (args) => {
|
|
806
|
+
const { action } = args;
|
|
807
|
+
if (action === "list") {
|
|
808
|
+
const requests = listPendingRequests();
|
|
809
|
+
if (requests.length === 0)
|
|
810
|
+
return "No pending access requests.";
|
|
811
|
+
const lines = requests.map(r => {
|
|
812
|
+
const who = r.username ? `@${r.username}` : r.firstName ?? String(r.userId);
|
|
813
|
+
const age = formatAge(r.requestedAt);
|
|
814
|
+
return `${who} (user ${r.userId}) → agent "${r.agentId}"\n Code: ${r.code} · ${age}`;
|
|
815
|
+
});
|
|
816
|
+
return lines.join("\n\n");
|
|
817
|
+
}
|
|
818
|
+
if (action === "approve") {
|
|
819
|
+
const code = args.code;
|
|
820
|
+
if (!code)
|
|
821
|
+
return "Error: code is required for approve.";
|
|
822
|
+
const request = approveRequest(code);
|
|
823
|
+
if (!request)
|
|
824
|
+
return "Request not found or already handled.";
|
|
825
|
+
const who = request.username ? `@${request.username}` : request.firstName ?? String(request.userId);
|
|
826
|
+
return `Approved: ${who} now has access to "${request.agentId}".`;
|
|
827
|
+
}
|
|
828
|
+
if (action === "deny") {
|
|
829
|
+
const code = args.code;
|
|
830
|
+
if (!code)
|
|
831
|
+
return "Error: code is required for deny.";
|
|
832
|
+
const request = denyRequest(code);
|
|
833
|
+
if (!request)
|
|
834
|
+
return "Request not found or already handled.";
|
|
835
|
+
const who = request.username ? `@${request.username}` : request.firstName ?? String(request.userId);
|
|
836
|
+
return `Denied access for ${who}.`;
|
|
837
|
+
}
|
|
838
|
+
return `Unknown action: ${action}`;
|
|
839
|
+
},
|
|
840
|
+
};
|
|
841
|
+
//# sourceMappingURL=admin.js.map
|