@sofer_agent/cli 0.6.0 → 0.7.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/dist/bin.js +13 -0
- package/dist/bin.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +171 -0
- package/dist/daemon.js.map +1 -0
- package/package.json +2 -2
- package/src/bin.ts +10 -0
- package/src/daemon.ts +177 -0
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { agentPaths } from "@sofer_agent/core";
|
|
3
3
|
import { chatCommand } from "./chat.js";
|
|
4
|
+
import { daemonStart, daemonStop } from "./daemon.js";
|
|
4
5
|
import { infoCommand } from "./info.js";
|
|
5
6
|
import { initCommand } from "./init.js";
|
|
6
7
|
import { telegramCommand } from "./telegram-start.js";
|
|
@@ -11,6 +12,8 @@ function printHelp() {
|
|
|
11
12
|
sofer --resume <id> resume a previous session
|
|
12
13
|
sofer init interactive setup — mint agent + provision memory
|
|
13
14
|
sofer info print on-chain agent state
|
|
15
|
+
sofer daemon start start persistent background agent
|
|
16
|
+
sofer daemon stop stop the background agent
|
|
14
17
|
sofer telegram start the Telegram bot
|
|
15
18
|
sofer help show this help
|
|
16
19
|
|
|
@@ -37,6 +40,16 @@ async function main() {
|
|
|
37
40
|
case "telegram":
|
|
38
41
|
await telegramCommand();
|
|
39
42
|
break;
|
|
43
|
+
case "daemon": {
|
|
44
|
+
const sub = args[1];
|
|
45
|
+
if (sub === "start")
|
|
46
|
+
await daemonStart();
|
|
47
|
+
else if (sub === "stop")
|
|
48
|
+
await daemonStop();
|
|
49
|
+
else
|
|
50
|
+
console.error("usage: sofer daemon <start|stop>");
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
40
53
|
case "help":
|
|
41
54
|
case "-h":
|
|
42
55
|
case "--help":
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CACT
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CACT;;;;;;;;;;;;6BAYyB,UAAU,CAAC,IAAI;gDACI,CAC7C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAE9B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC;QACZ,KAAK,UAAU;YACb,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACnD,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,UAAU;YACb,MAAM,eAAe,EAAE,CAAC;YACxB,MAAM;QACR,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,GAAG,KAAK,OAAO;gBAAE,MAAM,WAAW,EAAE,CAAC;iBACpC,IAAI,GAAG,KAAK,MAAM;gBAAE,MAAM,UAAU,EAAE,CAAC;;gBACvC,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACvD,MAAM;QACR,CAAC;QACD,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,SAAS,EAAE,CAAC;YACZ,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;YAC3C,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,IACE,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACpC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACjC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAChC,CAAC;QACD,OAAO,CAAC,KAAK,CACX,qEAAqE,UAAU,CAAC,IAAI,KAAK,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA6BA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAoIjD"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { agentPaths, decryptWithKey, loadConfig, readSecret, SoferAgent, } from "@sofer_agent/core";
|
|
5
|
+
import { TelegramListener } from "@sofer_agent/plugin-telegram";
|
|
6
|
+
import { loadSecrets } from "./env.js";
|
|
7
|
+
const PID_FILE = join(agentPaths.root, "daemon.pid");
|
|
8
|
+
function writePid(pid) {
|
|
9
|
+
writeFileSync(PID_FILE, String(pid), "utf8");
|
|
10
|
+
}
|
|
11
|
+
function readPid() {
|
|
12
|
+
try {
|
|
13
|
+
return Number(readFileSync(PID_FILE, "utf8").trim());
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function daemonStop() {
|
|
20
|
+
const pid = readPid();
|
|
21
|
+
if (!pid) {
|
|
22
|
+
console.log("No daemon running.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, "SIGTERM");
|
|
27
|
+
console.log(`Sent stop signal to PID ${pid}.`);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.log("Daemon not running (stale PID).");
|
|
31
|
+
unlinkSync(PID_FILE);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function daemonStart() {
|
|
35
|
+
const secrets = loadSecrets();
|
|
36
|
+
if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
|
|
37
|
+
console.error("Missing secrets. Run `sofer init` first.");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
const agent = await SoferAgent.create(config, {
|
|
42
|
+
suiSecretKey: secrets.suiSecretKey,
|
|
43
|
+
anthropicApiKey: secrets.anthropicApiKey,
|
|
44
|
+
});
|
|
45
|
+
// SSE clients
|
|
46
|
+
let sseId = 0;
|
|
47
|
+
const sseClients = new Set();
|
|
48
|
+
function broadcast(event, data) {
|
|
49
|
+
for (const c of sseClients) {
|
|
50
|
+
c.res.write(`event: ${event}\ndata: ${data}\n\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// HTTP server
|
|
54
|
+
const server = createServer(async (req, res) => {
|
|
55
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
56
|
+
if (req.method === "OPTIONS") {
|
|
57
|
+
res.writeHead(204).end();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// GET /healthz
|
|
61
|
+
if (req.url === "/healthz" || req.url === "/") {
|
|
62
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
63
|
+
res.end(JSON.stringify({ status: "ok", agent: agent.address.slice(0, 10), network: config.network }));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// GET /events (SSE)
|
|
67
|
+
if (req.url === "/events") {
|
|
68
|
+
res.writeHead(200, {
|
|
69
|
+
"content-type": "text/event-stream",
|
|
70
|
+
"cache-control": "no-cache",
|
|
71
|
+
"connection": "keep-alive",
|
|
72
|
+
});
|
|
73
|
+
const client = { id: ++sseId, res };
|
|
74
|
+
sseClients.add(client);
|
|
75
|
+
res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
|
|
76
|
+
req.on("close", () => sseClients.delete(client));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// POST /chat
|
|
80
|
+
if (req.method === "POST" && req.url === "/chat") {
|
|
81
|
+
const body = await readBody(req);
|
|
82
|
+
try {
|
|
83
|
+
const { message } = JSON.parse(body);
|
|
84
|
+
if (!message) {
|
|
85
|
+
res.writeHead(400).end("missing message");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
broadcast("thinking", JSON.stringify({ status: "thinking" }));
|
|
89
|
+
const { text } = await agent.chat(message);
|
|
90
|
+
broadcast("response", JSON.stringify({ text }));
|
|
91
|
+
broadcast("idle", JSON.stringify({ status: "idle" }));
|
|
92
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
93
|
+
res.end(JSON.stringify({ text }));
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
broadcast("error", JSON.stringify({ error: e.message }));
|
|
97
|
+
res.writeHead(500).end(e.message);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// GET /status
|
|
102
|
+
if (req.url === "/status") {
|
|
103
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
104
|
+
res.end(JSON.stringify({
|
|
105
|
+
agent: agent.address,
|
|
106
|
+
name: agent.name,
|
|
107
|
+
network: config.network,
|
|
108
|
+
tokens: agent.usage.tokens,
|
|
109
|
+
cost: agent.usage.costUsd.toFixed(6),
|
|
110
|
+
}));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
res.writeHead(404).end("not found");
|
|
114
|
+
});
|
|
115
|
+
// Telegram listener
|
|
116
|
+
const tgPath = `${agentPaths.root}/telegram.enc`;
|
|
117
|
+
const tgEnc = readSecret(tgPath);
|
|
118
|
+
let tgListener = null;
|
|
119
|
+
if (tgEnc) {
|
|
120
|
+
try {
|
|
121
|
+
const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
|
|
122
|
+
if (raw) {
|
|
123
|
+
const tgConfig = JSON.parse(raw);
|
|
124
|
+
tgListener = new TelegramListener({
|
|
125
|
+
botToken: tgConfig.botToken,
|
|
126
|
+
allowedUserIds: tgConfig.allowedUserIds,
|
|
127
|
+
agentName: agent.name.toLowerCase(),
|
|
128
|
+
});
|
|
129
|
+
tgListener.onDispatch(async (input) => {
|
|
130
|
+
broadcast("tg-message", JSON.stringify({ from: input.displayName, text: input.text.slice(0, 100) }));
|
|
131
|
+
const { text } = await agent.chat(input.text);
|
|
132
|
+
broadcast("tg-response", JSON.stringify({ text: text.slice(0, 100) }));
|
|
133
|
+
return text;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* skip */ }
|
|
138
|
+
}
|
|
139
|
+
const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
|
|
140
|
+
server.listen(PORT, () => {
|
|
141
|
+
writePid(process.pid);
|
|
142
|
+
console.log(`[daemon] sofer running on http://localhost:${PORT}`);
|
|
143
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
144
|
+
if (tgListener) {
|
|
145
|
+
console.log("[daemon] telegram: starting bot…");
|
|
146
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram failed:", e.message));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// Graceful shutdown
|
|
150
|
+
const shutdown = async () => {
|
|
151
|
+
console.log("\n[daemon] shutting down…");
|
|
152
|
+
if (tgListener)
|
|
153
|
+
await tgListener.stop();
|
|
154
|
+
server.close();
|
|
155
|
+
try {
|
|
156
|
+
unlinkSync(PID_FILE);
|
|
157
|
+
}
|
|
158
|
+
catch { /* */ }
|
|
159
|
+
process.exit(0);
|
|
160
|
+
};
|
|
161
|
+
process.on("SIGINT", shutdown);
|
|
162
|
+
process.on("SIGTERM", shutdown);
|
|
163
|
+
}
|
|
164
|
+
function readBody(req) {
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
let data = "";
|
|
167
|
+
req.on("data", (chunk) => { data += chunk; });
|
|
168
|
+
req.on("end", () => resolve(data));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofer_agent/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sofer": "./bin/sofer"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@clack/prompts": "^1.5.1",
|
|
30
30
|
"@types/blessed": "^0.1.27",
|
|
31
31
|
"blessed": "^0.1.81",
|
|
32
|
-
"@sofer_agent/core": "0.
|
|
32
|
+
"@sofer_agent/core": "0.6.0",
|
|
33
33
|
"@sofer_agent/plugin-telegram": "0.1.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
package/src/bin.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { agentPaths } from "@sofer_agent/core";
|
|
3
3
|
import { chatCommand } from "./chat.js";
|
|
4
|
+
import { daemonStart, daemonStop } from "./daemon.js";
|
|
4
5
|
import { infoCommand } from "./info.js";
|
|
5
6
|
import { initCommand } from "./init.js";
|
|
6
7
|
import { telegramCommand } from "./telegram-start.js";
|
|
@@ -13,6 +14,8 @@ function printHelp(): void {
|
|
|
13
14
|
sofer --resume <id> resume a previous session
|
|
14
15
|
sofer init interactive setup — mint agent + provision memory
|
|
15
16
|
sofer info print on-chain agent state
|
|
17
|
+
sofer daemon start start persistent background agent
|
|
18
|
+
sofer daemon stop stop the background agent
|
|
16
19
|
sofer telegram start the Telegram bot
|
|
17
20
|
sofer help show this help
|
|
18
21
|
|
|
@@ -42,6 +45,13 @@ async function main(): Promise<void> {
|
|
|
42
45
|
case "telegram":
|
|
43
46
|
await telegramCommand();
|
|
44
47
|
break;
|
|
48
|
+
case "daemon": {
|
|
49
|
+
const sub = args[1];
|
|
50
|
+
if (sub === "start") await daemonStart();
|
|
51
|
+
else if (sub === "stop") await daemonStop();
|
|
52
|
+
else console.error("usage: sofer daemon <start|stop>");
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
45
55
|
case "help":
|
|
46
56
|
case "-h":
|
|
47
57
|
case "--help":
|
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
type SoferConfig,
|
|
6
|
+
agentPaths,
|
|
7
|
+
decryptWithKey,
|
|
8
|
+
loadConfig,
|
|
9
|
+
readSecret,
|
|
10
|
+
SoferAgent,
|
|
11
|
+
} from "@sofer_agent/core";
|
|
12
|
+
import { TelegramListener } from "@sofer_agent/plugin-telegram";
|
|
13
|
+
import { loadSecrets } from "./env.js";
|
|
14
|
+
|
|
15
|
+
const PID_FILE = join(agentPaths.root, "daemon.pid");
|
|
16
|
+
|
|
17
|
+
interface SSEClient {
|
|
18
|
+
id: number;
|
|
19
|
+
res: ServerResponse;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writePid(pid: number): void {
|
|
23
|
+
writeFileSync(PID_FILE, String(pid), "utf8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readPid(): number | null {
|
|
27
|
+
try { return Number(readFileSync(PID_FILE, "utf8").trim()); } catch { return null; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function daemonStop(): Promise<void> {
|
|
31
|
+
const pid = readPid();
|
|
32
|
+
if (!pid) { console.log("No daemon running."); return; }
|
|
33
|
+
try { process.kill(pid, "SIGTERM"); console.log(`Sent stop signal to PID ${pid}.`); }
|
|
34
|
+
catch { console.log("Daemon not running (stale PID)."); unlinkSync(PID_FILE); }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function daemonStart(): Promise<void> {
|
|
38
|
+
const secrets = loadSecrets();
|
|
39
|
+
if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
|
|
40
|
+
console.error("Missing secrets. Run `sofer init` first.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = loadConfig();
|
|
45
|
+
const agent = await SoferAgent.create(config, {
|
|
46
|
+
suiSecretKey: secrets.suiSecretKey,
|
|
47
|
+
anthropicApiKey: secrets.anthropicApiKey,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// SSE clients
|
|
51
|
+
let sseId = 0;
|
|
52
|
+
const sseClients = new Set<SSEClient>();
|
|
53
|
+
|
|
54
|
+
function broadcast(event: string, data: string): void {
|
|
55
|
+
for (const c of sseClients) {
|
|
56
|
+
c.res.write(`event: ${event}\ndata: ${data}\n\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// HTTP server
|
|
61
|
+
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
62
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
63
|
+
|
|
64
|
+
if (req.method === "OPTIONS") { res.writeHead(204).end(); return; }
|
|
65
|
+
|
|
66
|
+
// GET /healthz
|
|
67
|
+
if (req.url === "/healthz" || req.url === "/") {
|
|
68
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
69
|
+
res.end(JSON.stringify({ status: "ok", agent: agent.address.slice(0, 10), network: config.network }));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// GET /events (SSE)
|
|
74
|
+
if (req.url === "/events") {
|
|
75
|
+
res.writeHead(200, {
|
|
76
|
+
"content-type": "text/event-stream",
|
|
77
|
+
"cache-control": "no-cache",
|
|
78
|
+
"connection": "keep-alive",
|
|
79
|
+
});
|
|
80
|
+
const client: SSEClient = { id: ++sseId, res };
|
|
81
|
+
sseClients.add(client);
|
|
82
|
+
res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
|
|
83
|
+
req.on("close", () => sseClients.delete(client));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// POST /chat
|
|
88
|
+
if (req.method === "POST" && req.url === "/chat") {
|
|
89
|
+
const body = await readBody(req);
|
|
90
|
+
try {
|
|
91
|
+
const { message } = JSON.parse(body);
|
|
92
|
+
if (!message) { res.writeHead(400).end("missing message"); return; }
|
|
93
|
+
|
|
94
|
+
broadcast("thinking", JSON.stringify({ status: "thinking" }));
|
|
95
|
+
const { text } = await agent.chat(message);
|
|
96
|
+
broadcast("response", JSON.stringify({ text }));
|
|
97
|
+
broadcast("idle", JSON.stringify({ status: "idle" }));
|
|
98
|
+
|
|
99
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
100
|
+
res.end(JSON.stringify({ text }));
|
|
101
|
+
} catch (e) {
|
|
102
|
+
broadcast("error", JSON.stringify({ error: (e as Error).message }));
|
|
103
|
+
res.writeHead(500).end((e as Error).message);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// GET /status
|
|
109
|
+
if (req.url === "/status") {
|
|
110
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
111
|
+
res.end(JSON.stringify({
|
|
112
|
+
agent: agent.address,
|
|
113
|
+
name: agent.name,
|
|
114
|
+
network: config.network,
|
|
115
|
+
tokens: agent.usage.tokens,
|
|
116
|
+
cost: agent.usage.costUsd.toFixed(6),
|
|
117
|
+
}));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
res.writeHead(404).end("not found");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Telegram listener
|
|
125
|
+
const tgPath = `${agentPaths.root}/telegram.enc`;
|
|
126
|
+
const tgEnc = readSecret(tgPath);
|
|
127
|
+
let tgListener: TelegramListener | null = null;
|
|
128
|
+
if (tgEnc) {
|
|
129
|
+
try {
|
|
130
|
+
const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
|
|
131
|
+
if (raw) {
|
|
132
|
+
const tgConfig = JSON.parse(raw) as { botToken: string; allowedUserIds: number[] };
|
|
133
|
+
tgListener = new TelegramListener({
|
|
134
|
+
botToken: tgConfig.botToken,
|
|
135
|
+
allowedUserIds: tgConfig.allowedUserIds,
|
|
136
|
+
agentName: agent.name.toLowerCase(),
|
|
137
|
+
});
|
|
138
|
+
tgListener.onDispatch(async (input) => {
|
|
139
|
+
broadcast("tg-message", JSON.stringify({ from: input.displayName, text: input.text.slice(0, 100) }));
|
|
140
|
+
const { text } = await agent.chat(input.text);
|
|
141
|
+
broadcast("tg-response", JSON.stringify({ text: text.slice(0, 100) }));
|
|
142
|
+
return text;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} catch { /* skip */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
|
|
149
|
+
server.listen(PORT, () => {
|
|
150
|
+
writePid(process.pid);
|
|
151
|
+
console.log(`[daemon] sofer running on http://localhost:${PORT}`);
|
|
152
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
153
|
+
if (tgListener) {
|
|
154
|
+
console.log("[daemon] telegram: starting bot…");
|
|
155
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram failed:", e.message));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Graceful shutdown
|
|
160
|
+
const shutdown = async () => {
|
|
161
|
+
console.log("\n[daemon] shutting down…");
|
|
162
|
+
if (tgListener) await tgListener.stop();
|
|
163
|
+
server.close();
|
|
164
|
+
try { unlinkSync(PID_FILE); } catch { /* */ }
|
|
165
|
+
process.exit(0);
|
|
166
|
+
};
|
|
167
|
+
process.on("SIGINT", shutdown);
|
|
168
|
+
process.on("SIGTERM", shutdown);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
let data = "";
|
|
174
|
+
req.on("data", (chunk) => { data += chunk; });
|
|
175
|
+
req.on("end", () => resolve(data));
|
|
176
|
+
});
|
|
177
|
+
}
|