@gonzih/agent-ops 0.3.0 → 0.4.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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Lightweight HTTP control endpoint for a single cc-tg instance.
3
+ *
4
+ * Exposes:
5
+ * GET /status — current agent info + uptime
6
+ * POST /restart — graceful exit (launchd respawns = auto-update)
7
+ * GET /logs — last N lines of the log file (?lines=100)
8
+ *
9
+ * No framework deps — plain Node http module.
10
+ */
11
+ import http from "node:http";
12
+ import { AgentRecord } from "./registry.js";
13
+ export interface ControlServerOptions {
14
+ port: number;
15
+ logFile?: string;
16
+ agentRecord: Omit<AgentRecord, "last_seen">;
17
+ authToken?: string;
18
+ }
19
+ export declare function createControlServer(opts: ControlServerOptions): http.Server;
20
+ //# sourceMappingURL=control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../src/control.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAwD3E"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Lightweight HTTP control endpoint for a single cc-tg instance.
3
+ *
4
+ * Exposes:
5
+ * GET /status — current agent info + uptime
6
+ * POST /restart — graceful exit (launchd respawns = auto-update)
7
+ * GET /logs — last N lines of the log file (?lines=100)
8
+ *
9
+ * No framework deps — plain Node http module.
10
+ */
11
+ import http from "node:http";
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ function json(res, status, body) {
15
+ const payload = JSON.stringify(body);
16
+ res.writeHead(status, {
17
+ "Content-Type": "application/json",
18
+ "Content-Length": Buffer.byteLength(payload),
19
+ });
20
+ res.end(payload);
21
+ }
22
+ function tailFile(filePath, lines) {
23
+ try {
24
+ const content = fs.readFileSync(filePath, "utf8");
25
+ const all = content.split("\n");
26
+ return all.slice(Math.max(0, all.length - lines)).join("\n");
27
+ }
28
+ catch (err) {
29
+ return `(could not read log file: ${err.message})`;
30
+ }
31
+ }
32
+ export function createControlServer(opts) {
33
+ const startedAt = new Date().toISOString();
34
+ const server = http.createServer((req, res) => {
35
+ // Optional auth
36
+ if (opts.authToken) {
37
+ const auth = req.headers["authorization"] ?? "";
38
+ if (auth !== `Bearer ${opts.authToken}`) {
39
+ return json(res, 401, { error: "unauthorized" });
40
+ }
41
+ }
42
+ const url = new URL(req.url ?? "/", `http://localhost:${opts.port}`);
43
+ if (req.method === "GET" && url.pathname === "/status") {
44
+ return json(res, 200, {
45
+ ...opts.agentRecord,
46
+ started_at: startedAt,
47
+ last_seen: new Date().toISOString(),
48
+ uptime_seconds: Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000),
49
+ });
50
+ }
51
+ if (req.method === "POST" && url.pathname === "/restart") {
52
+ json(res, 200, { ok: true, message: "restarting" });
53
+ // Give the response time to flush before exiting
54
+ setTimeout(() => process.exit(0), 200);
55
+ return;
56
+ }
57
+ if (req.method === "GET" && url.pathname === "/logs") {
58
+ const lines = parseInt(url.searchParams.get("lines") ?? "100", 10);
59
+ const logFile = opts.logFile ?? path.join("/tmp", `cc-tg-${opts.agentRecord.namespace}.log`);
60
+ return json(res, 200, {
61
+ file: logFile,
62
+ lines,
63
+ content: tailFile(logFile, lines),
64
+ });
65
+ }
66
+ if (req.method === "GET" && url.pathname === "/metrics") {
67
+ return json(res, 200, {
68
+ namespace: opts.agentRecord.namespace,
69
+ token_count: 0,
70
+ cost_usd: 0,
71
+ });
72
+ }
73
+ return json(res, 404, { error: "not found" });
74
+ });
75
+ server.listen(opts.port, () => {
76
+ console.log(`[agent-ops/control] listening on :${opts.port}`);
77
+ });
78
+ return server;
79
+ }
80
+ //# sourceMappingURL=control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.js","sourceRoot":"","sources":["../src/control.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAU7B,SAAS,IAAI,CAAC,GAAwB,EAAE,MAAc,EAAE,IAAa;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,kBAAkB;QAClC,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;KAC7C,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,QAAQ,CAAC,QAAgB,EAAE,KAAa;IAC/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,6BAA8B,GAAa,CAAC,OAAO,GAAG,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAA0B;IAC5D,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,gBAAgB;QAChB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;YAChD,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;gBACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAErE,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBACpB,GAAG,IAAI,CAAC,WAAW;gBACnB,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;aAChF,CAAC,CAAC;QACL,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YACzD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;YACpD,iDAAiD;YACjD,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;YACnE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,WAAW,CAAC,SAAS,MAAM,CAAC,CAAC;YAC7F,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBACpB,IAAI,EAAE,OAAO;gBACb,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;aAClC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YACxD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBACpB,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS;gBACrC,WAAW,EAAE,CAAC;gBACd,QAAQ,EAAE,CAAC;aACZ,CAAC,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE;QAC5B,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { AgentRegistry } from "./registry.js";
2
+ export type { AgentRecord } from "./registry.js";
3
+ export { createControlServer } from "./control.js";
4
+ export type { ControlServerOptions } from "./control.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { AgentRegistry } from "./registry.js";
2
+ export { createControlServer } from "./control.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Telegram ops bot — single bot that manages the entire cc-tg agent fleet.
3
+ *
4
+ * Commands:
5
+ * /agents — list all registered agents with liveness status
6
+ * /health — summary health view
7
+ * /restart <id> — POST /restart to named agent's control endpoint
8
+ * /logs <id> — tail logs from named agent
9
+ * /update all — restart every agent (launchd respawn = auto-update)
10
+ * /broadcast <msg> — send a message via each agent's Telegram bot token
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=ops-bot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops-bot.d.ts","sourceRoot":"","sources":["../src/ops-bot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Telegram ops bot — single bot that manages the entire cc-tg agent fleet.
3
+ *
4
+ * Commands:
5
+ * /agents — list all registered agents with liveness status
6
+ * /health — summary health view
7
+ * /restart <id> — POST /restart to named agent's control endpoint
8
+ * /logs <id> — tail logs from named agent
9
+ * /update all — restart every agent (launchd respawn = auto-update)
10
+ * /broadcast <msg> — send a message via each agent's Telegram bot token
11
+ */
12
+ import TelegramBot from "node-telegram-bot-api";
13
+ import { AgentRegistry } from "./registry.js";
14
+ const BOT_TOKEN = process.env.OPS_BOT_TOKEN;
15
+ const REDIS_URL = process.env.REDIS_URL ?? "redis://localhost:6379";
16
+ const CONTROL_AUTH_TOKEN = process.env.CONTROL_AUTH_TOKEN;
17
+ const ALLOWED_CHAT_IDS = (process.env.ALLOWED_CHAT_IDS ?? "")
18
+ .split(",")
19
+ .map((s) => s.trim())
20
+ .filter(Boolean);
21
+ if (!BOT_TOKEN) {
22
+ console.error("OPS_BOT_TOKEN env var is required");
23
+ process.exit(1);
24
+ }
25
+ const registry = new AgentRegistry(REDIS_URL);
26
+ const bot = new TelegramBot(BOT_TOKEN, { polling: true });
27
+ async function controlFetch(agent, path, method = "GET") {
28
+ const headers = { "Content-Type": "application/json" };
29
+ if (CONTROL_AUTH_TOKEN)
30
+ headers["Authorization"] = `Bearer ${CONTROL_AUTH_TOKEN}`;
31
+ return fetch(`${agent.control_url}${path}`, { method, headers });
32
+ }
33
+ function agentLine(a) {
34
+ const ago = Math.floor((Date.now() - new Date(a.last_seen).getTime()) / 1000);
35
+ const status = ago < 90 ? "✅" : "❌";
36
+ return `${status} \`${a.id}\` [${a.type ?? 'cc-tg'}] — ${a.bot_username} @ ${a.hostname} (${ago}s ago)`;
37
+ }
38
+ function isAllowed(chatId) {
39
+ if (ALLOWED_CHAT_IDS.length === 0)
40
+ return true; // open if not configured
41
+ return ALLOWED_CHAT_IDS.includes(String(chatId));
42
+ }
43
+ bot.onText(/\/agents/, async (msg) => {
44
+ if (!isAllowed(msg.chat.id))
45
+ return;
46
+ const agents = await registry.list();
47
+ if (agents.length === 0) {
48
+ return bot.sendMessage(msg.chat.id, "No agents registered.");
49
+ }
50
+ const lines = agents.map(agentLine).join("\n");
51
+ bot.sendMessage(msg.chat.id, `*Registered agents (${agents.length}):*\n${lines}`, {
52
+ parse_mode: "Markdown",
53
+ });
54
+ });
55
+ bot.onText(/\/health/, async (msg) => {
56
+ if (!isAllowed(msg.chat.id))
57
+ return;
58
+ const agents = await registry.list();
59
+ const alive = agents.filter((a) => (Date.now() - new Date(a.last_seen).getTime()) / 1000 < 90);
60
+ const dead = agents.length - alive.length;
61
+ bot.sendMessage(msg.chat.id, `*Fleet health:*\n✅ Alive: ${alive.length}\n❌ Dead/stale: ${dead}\nTotal: ${agents.length}`, { parse_mode: "Markdown" });
62
+ });
63
+ bot.onText(/\/restart (.+)/, async (msg, match) => {
64
+ if (!isAllowed(msg.chat.id))
65
+ return;
66
+ const id = match?.[1]?.trim();
67
+ if (!id)
68
+ return bot.sendMessage(msg.chat.id, "Usage: /restart <agent-id>");
69
+ const agent = await registry.get(id);
70
+ if (!agent)
71
+ return bot.sendMessage(msg.chat.id, `Agent \`${id}\` not found.`, { parse_mode: "Markdown" });
72
+ try {
73
+ const res = await controlFetch(agent, "/restart", "POST");
74
+ const body = await res.json();
75
+ bot.sendMessage(msg.chat.id, `Restarting \`${id}\`… ${JSON.stringify(body)}`, { parse_mode: "Markdown" });
76
+ }
77
+ catch (err) {
78
+ bot.sendMessage(msg.chat.id, `Failed to reach \`${id}\`: ${err.message}`, { parse_mode: "Markdown" });
79
+ }
80
+ });
81
+ bot.onText(/\/logs (.+)/, async (msg, match) => {
82
+ if (!isAllowed(msg.chat.id))
83
+ return;
84
+ const id = match?.[1]?.trim();
85
+ if (!id)
86
+ return bot.sendMessage(msg.chat.id, "Usage: /logs <agent-id>");
87
+ const agent = await registry.get(id);
88
+ if (!agent)
89
+ return bot.sendMessage(msg.chat.id, `Agent \`${id}\` not found.`, { parse_mode: "Markdown" });
90
+ try {
91
+ const res = await controlFetch(agent, "/logs?lines=50");
92
+ const body = await res.json();
93
+ const snippet = body.content.slice(-3000); // Telegram message limit
94
+ bot.sendMessage(msg.chat.id, `*Logs for \`${id}\`:*\n\`\`\`\n${snippet}\n\`\`\``, { parse_mode: "Markdown" });
95
+ }
96
+ catch (err) {
97
+ bot.sendMessage(msg.chat.id, `Failed to reach \`${id}\`: ${err.message}`, { parse_mode: "Markdown" });
98
+ }
99
+ });
100
+ bot.onText(/\/update all/, async (msg) => {
101
+ if (!isAllowed(msg.chat.id))
102
+ return;
103
+ const agents = await registry.list();
104
+ if (agents.length === 0)
105
+ return bot.sendMessage(msg.chat.id, "No agents to update.");
106
+ bot.sendMessage(msg.chat.id, `Triggering restart on ${agents.length} agent(s)…`);
107
+ const results = await Promise.allSettled(agents.map((a) => controlFetch(a, "/restart", "POST")));
108
+ const ok = results.filter((r) => r.status === "fulfilled").length;
109
+ bot.sendMessage(msg.chat.id, `Done. ${ok}/${agents.length} responded to restart.`);
110
+ });
111
+ bot.onText(/\/broadcast (.+)/, async (msg, match) => {
112
+ if (!isAllowed(msg.chat.id))
113
+ return;
114
+ const message = match?.[1]?.trim();
115
+ if (!message)
116
+ return bot.sendMessage(msg.chat.id, "Usage: /broadcast <message>");
117
+ // Broadcast is a no-op in the ops bot itself — it's a reminder / template
118
+ // that each cc-tg instance handles /broadcast via its own bot token.
119
+ bot.sendMessage(msg.chat.id, `Broadcast "\`${message}\`" — send this command to each agent's chat directly, or integrate with the control endpoint.`, { parse_mode: "Markdown" });
120
+ });
121
+ bot.onText(/\/metrics/, async (msg) => {
122
+ if (!isAllowed(msg.chat.id))
123
+ return;
124
+ const agents = await registry.list();
125
+ if (agents.length === 0)
126
+ return bot.sendMessage(msg.chat.id, "No agents registered.");
127
+ const results = await Promise.allSettled(agents.map(async (a) => {
128
+ const res = await controlFetch(a, "/metrics");
129
+ if (!res.ok)
130
+ throw new Error(`HTTP ${res.status}`);
131
+ return res.json();
132
+ }));
133
+ let totalTokens = 0;
134
+ let totalCost = 0;
135
+ let supported = 0;
136
+ for (const r of results) {
137
+ if (r.status === "fulfilled") {
138
+ totalTokens += r.value.token_count ?? 0;
139
+ totalCost += r.value.cost_usd ?? 0;
140
+ supported++;
141
+ }
142
+ }
143
+ bot.sendMessage(msg.chat.id, `*Fleet metrics (${supported}/${agents.length} agents reporting):*\nTokens: \`${totalTokens.toLocaleString()}\`\nCost: \`$${totalCost.toFixed(4)}\``, { parse_mode: "Markdown" });
144
+ });
145
+ registry.connect().then(() => {
146
+ console.log("[agent-ops/ops-bot] connected to Redis, polling Telegram…");
147
+ });
148
+ //# sourceMappingURL=ops-bot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops-bot.js","sourceRoot":"","sources":["../src/ops-bot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Redis-backed agent registry with heartbeat + TTL liveness.
3
+ *
4
+ * Each cc-tg instance self-registers on start and sends heartbeats every 60s.
5
+ * Keys expire after 120s — any agent that misses 2 heartbeats is considered dead.
6
+ */
7
+ export interface AgentRecord {
8
+ id: string;
9
+ type: 'cc-tg' | 'openclaw' | 'codex' | 'ollama' | 'custom';
10
+ hostname: string;
11
+ user: string;
12
+ bot_username: string;
13
+ cwd: string;
14
+ namespace: string;
15
+ pid: number;
16
+ version: string;
17
+ control_url: string;
18
+ started_at: string;
19
+ last_seen: string;
20
+ }
21
+ export declare class AgentRegistry {
22
+ private redis;
23
+ constructor(redisUrl?: string);
24
+ connect(): Promise<void>;
25
+ disconnect(): Promise<void>;
26
+ /** Register or update an agent record. Call on start and every heartbeat. */
27
+ register(record: AgentRecord): Promise<void>;
28
+ /** Refresh TTL for a live agent — call every 60s from cc-tg. */
29
+ heartbeat(id: string): Promise<void>;
30
+ /** Deregister an agent on clean shutdown. */
31
+ deregister(id: string): Promise<void>;
32
+ /** List all currently-live agents. */
33
+ list(): Promise<AgentRecord[]>;
34
+ /** Get a single agent by id. Returns null if not found / expired. */
35
+ get(id: string): Promise<AgentRecord | null>;
36
+ }
37
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAA6B;gBAE9B,QAAQ,GAAE,MAA0D;IAI1E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,6EAA6E;IACvE,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlD,gEAAgE;IAC1D,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,6CAA6C;IACvC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,sCAAsC;IAChC,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IASpC,qEAAqE;IAC/D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CAInD"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Redis-backed agent registry with heartbeat + TTL liveness.
3
+ *
4
+ * Each cc-tg instance self-registers on start and sends heartbeats every 60s.
5
+ * Keys expire after 120s — any agent that misses 2 heartbeats is considered dead.
6
+ */
7
+ import { Redis } from "ioredis";
8
+ const REGISTRY_PREFIX = "agent-ops:agent:";
9
+ const TTL_SECONDS = 120;
10
+ export class AgentRegistry {
11
+ redis;
12
+ constructor(redisUrl = process.env.REDIS_URL ?? "redis://localhost:6379") {
13
+ this.redis = new Redis(redisUrl, { lazyConnect: true });
14
+ }
15
+ async connect() {
16
+ await this.redis.connect();
17
+ }
18
+ async disconnect() {
19
+ await this.redis.quit();
20
+ }
21
+ /** Register or update an agent record. Call on start and every heartbeat. */
22
+ async register(record) {
23
+ const key = `${REGISTRY_PREFIX}${record.id}`;
24
+ const value = JSON.stringify({ ...record, last_seen: new Date().toISOString() });
25
+ await this.redis.set(key, value, "EX", TTL_SECONDS);
26
+ }
27
+ /** Refresh TTL for a live agent — call every 60s from cc-tg. */
28
+ async heartbeat(id) {
29
+ const key = `${REGISTRY_PREFIX}${id}`;
30
+ const raw = await this.redis.get(key);
31
+ if (!raw)
32
+ return; // never registered or already expired
33
+ const record = JSON.parse(raw);
34
+ record.last_seen = new Date().toISOString();
35
+ await this.redis.set(key, JSON.stringify(record), "EX", TTL_SECONDS);
36
+ }
37
+ /** Deregister an agent on clean shutdown. */
38
+ async deregister(id) {
39
+ await this.redis.del(`${REGISTRY_PREFIX}${id}`);
40
+ }
41
+ /** List all currently-live agents. */
42
+ async list() {
43
+ const keys = await this.redis.keys(`${REGISTRY_PREFIX}*`);
44
+ if (keys.length === 0)
45
+ return [];
46
+ const values = await this.redis.mget(...keys);
47
+ return values
48
+ .filter((v) => v !== null)
49
+ .map((v) => JSON.parse(v));
50
+ }
51
+ /** Get a single agent by id. Returns null if not found / expired. */
52
+ async get(id) {
53
+ const raw = await this.redis.get(`${REGISTRY_PREFIX}${id}`);
54
+ return raw ? JSON.parse(raw) : null;
55
+ }
56
+ }
57
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gonzih/agent-ops",
3
- "version": "0.3.0",
4
- "description": "Ops layer for cc-tg agent fleet — discovery, control, log aggregation",
3
+ "version": "0.4.0",
4
+ "description": "Universal agent control plane — type-agnostic discovery, control, metrics aggregation for cc-tg, openclaw, codex, ollama, and custom agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
@@ -18,7 +18,11 @@
18
18
  "agents",
19
19
  "ops",
20
20
  "telegram",
21
- "cc-tg"
21
+ "cc-tg",
22
+ "openclaw",
23
+ "codex",
24
+ "ollama",
25
+ "universal"
22
26
  ],
23
27
  "author": "ecoclaw",
24
28
  "license": "MIT",