@gonzih/agent-ops 0.2.0 → 0.3.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/package.json +11 -3
- package/dist/control.d.ts +0 -20
- package/dist/control.d.ts.map +0 -1
- package/dist/control.js +0 -73
- package/dist/control.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/ops-bot.d.ts +0 -13
- package/dist/ops-bot.d.ts.map +0 -1
- package/dist/ops-bot.js +0 -124
- package/dist/ops-bot.js.map +0 -1
- package/dist/registry.d.ts +0 -36
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -57
- package/dist/registry.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonzih/agent-ops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Ops layer for cc-tg agent fleet — discovery, control, log aggregation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
"start": "node dist/ops-bot.js",
|
|
11
11
|
"dev": "tsx src/ops-bot.ts"
|
|
12
12
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"claude",
|
|
18
|
+
"agents",
|
|
19
|
+
"ops",
|
|
20
|
+
"telegram",
|
|
21
|
+
"cc-tg"
|
|
22
|
+
],
|
|
15
23
|
"author": "ecoclaw",
|
|
16
24
|
"license": "MIT",
|
|
17
25
|
"dependencies": {
|
package/dist/control.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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
|
package/dist/control.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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,CAgD3E"}
|
package/dist/control.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
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
|
-
return json(res, 404, { error: "not found" });
|
|
67
|
-
});
|
|
68
|
-
server.listen(opts.port, () => {
|
|
69
|
-
console.log(`[agent-ops/control] listening on :${opts.port}`);
|
|
70
|
-
});
|
|
71
|
-
return server;
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=control.js.map
|
package/dist/control.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"control.js","sourceRoot":"","sources":["../src/control.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/ops-bot.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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
|
package/dist/ops-bot.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ops-bot.d.ts","sourceRoot":"","sources":["../src/ops-bot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
package/dist/ops-bot.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
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.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
|
-
registry.connect().then(() => {
|
|
122
|
-
console.log("[agent-ops/ops-bot] connected to Redis, polling Telegram…");
|
|
123
|
-
});
|
|
124
|
-
//# sourceMappingURL=ops-bot.js.map
|
package/dist/ops-bot.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ops-bot.js","sourceRoot":"","sources":["../src/ops-bot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAe,MAAM,eAAe,CAAC;AAE3D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAC5C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,wBAAwB,CAAC;AACpE,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAC1D,MAAM,gBAAgB,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;KAC1D,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;KACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,IAAI,CAAC,SAAS,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,QAAQ,GAAG,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AAE1D,KAAK,UAAU,YAAY,CAAC,KAAkB,EAAE,IAAY,EAAE,MAAM,GAAG,KAAK;IAC1E,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC/E,IAAI,kBAAkB;QAAE,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,kBAAkB,EAAE,CAAC;IAClF,OAAO,KAAK,CAAC,GAAG,KAAK,CAAC,WAAW,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,SAAS,CAAC,CAAc;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACpC,OAAO,GAAG,MAAM,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,YAAY,MAAM,CAAC,CAAC,QAAQ,KAAK,GAAG,QAAQ,CAAC;AACnF,CAAC;AAED,SAAS,SAAS,CAAC,MAAc;IAC/B,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,yBAAyB;IACzE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,uBAAuB,MAAM,CAAC,MAAM,QAAQ,KAAK,EAAE,EAAE;QAChF,UAAU,EAAE,UAAU;KACvB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,EAAE,CAClE,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1C,GAAG,CAAC,WAAW,CACb,GAAG,CAAC,IAAI,CAAC,EAAE,EACX,6BAA6B,KAAK,CAAC,MAAM,mBAAmB,IAAI,YAAY,MAAM,CAAC,MAAM,EAAE,EAC3F,EAAE,UAAU,EAAE,UAAU,EAAE,CAC3B,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAChD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,4BAA4B,CAAC,CAAC;IAE3E,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAE1G,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,EAAE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5G,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,EAAE,OAAQ,GAAa,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACnH,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAC7C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,yBAAyB,CAAC,CAAC;IAExE,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAE1G,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAyB,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,yBAAyB;QACpE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,EAAE,iBAAiB,OAAO,UAAU,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAChH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,EAAE,OAAQ,GAAa,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACnH,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,sBAAsB,CAAC,CAAC;IAErF,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,yBAAyB,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CACvD,CAAC;IACF,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;IAClE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,MAAM,CAAC,MAAM,wBAAwB,CAAC,CAAC;AACrF,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,kBAAkB,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAClD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO;IACpC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,6BAA6B,CAAC,CAAC;IACjF,0EAA0E;IAC1E,qEAAqE;IACrE,GAAG,CAAC,WAAW,CACb,GAAG,CAAC,IAAI,CAAC,EAAE,EACX,gBAAgB,OAAO,gGAAgG,EACvH,EAAE,UAAU,EAAE,UAAU,EAAE,CAC3B,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IAC3B,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC"}
|
package/dist/registry.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
-
hostname: string;
|
|
10
|
-
user: string;
|
|
11
|
-
bot_username: string;
|
|
12
|
-
cwd: string;
|
|
13
|
-
namespace: string;
|
|
14
|
-
pid: number;
|
|
15
|
-
version: string;
|
|
16
|
-
control_url: string;
|
|
17
|
-
started_at: string;
|
|
18
|
-
last_seen: string;
|
|
19
|
-
}
|
|
20
|
-
export declare class AgentRegistry {
|
|
21
|
-
private redis;
|
|
22
|
-
constructor(redisUrl?: string);
|
|
23
|
-
connect(): Promise<void>;
|
|
24
|
-
disconnect(): Promise<void>;
|
|
25
|
-
/** Register or update an agent record. Call on start and every heartbeat. */
|
|
26
|
-
register(record: AgentRecord): Promise<void>;
|
|
27
|
-
/** Refresh TTL for a live agent — call every 60s from cc-tg. */
|
|
28
|
-
heartbeat(id: string): Promise<void>;
|
|
29
|
-
/** Deregister an agent on clean shutdown. */
|
|
30
|
-
deregister(id: string): Promise<void>;
|
|
31
|
-
/** List all currently-live agents. */
|
|
32
|
-
list(): Promise<AgentRecord[]>;
|
|
33
|
-
/** Get a single agent by id. Returns null if not found / expired. */
|
|
34
|
-
get(id: string): Promise<AgentRecord | null>;
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=registry.d.ts.map
|
package/dist/registry.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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,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"}
|
package/dist/registry.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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
|
package/dist/registry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|