@sofer_agent/cli 0.7.0 → 0.7.1
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/daemon.d.ts.map +1 -1
- package/dist/daemon.js +112 -45
- package/dist/daemon.js.map +1 -1
- package/package.json +1 -1
- package/src/daemon.ts +115 -50
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA2CA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAuLjD"}
|
package/dist/daemon.js
CHANGED
|
@@ -1,74 +1,106 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import {
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { agentPaths, decryptWithKey, loadConfig, readSecret, SoferAgent, } from "@sofer_agent/core";
|
|
4
|
+
import { agentPaths, decryptWithKey, loadConfig, readSecret, shortAgentId, SoferAgent, } from "@sofer_agent/core";
|
|
5
5
|
import { TelegramListener } from "@sofer_agent/plugin-telegram";
|
|
6
6
|
import { loadSecrets } from "./env.js";
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function readPid() {
|
|
7
|
+
const LOCK_FILE = join(agentPaths.root, "daemon.lock");
|
|
8
|
+
const SOCK_PATH = join(agentPaths.root, "daemon.sock");
|
|
9
|
+
// ── Process lock ──────────────────────────────────────────────────────────
|
|
10
|
+
function acquireLock() {
|
|
12
11
|
try {
|
|
13
|
-
|
|
12
|
+
if (existsSync(LOCK_FILE)) {
|
|
13
|
+
const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
|
|
14
|
+
try {
|
|
15
|
+
process.kill(pid, 0);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
catch { /* stale */ }
|
|
19
|
+
}
|
|
20
|
+
writeFileSync(LOCK_FILE, String(process.pid), "utf8");
|
|
21
|
+
return true;
|
|
14
22
|
}
|
|
15
23
|
catch {
|
|
16
|
-
return
|
|
24
|
+
return false;
|
|
17
25
|
}
|
|
18
26
|
}
|
|
27
|
+
function releaseLock() {
|
|
28
|
+
try {
|
|
29
|
+
if (existsSync(LOCK_FILE))
|
|
30
|
+
unlinkSync(LOCK_FILE);
|
|
31
|
+
}
|
|
32
|
+
catch { /* */ }
|
|
33
|
+
}
|
|
34
|
+
let state = "bootstrapping";
|
|
35
|
+
// ── Main ──────────────────────────────────────────────────────────────────
|
|
19
36
|
export async function daemonStop() {
|
|
20
|
-
|
|
21
|
-
if (!pid) {
|
|
37
|
+
if (!existsSync(LOCK_FILE)) {
|
|
22
38
|
console.log("No daemon running.");
|
|
23
39
|
return;
|
|
24
40
|
}
|
|
41
|
+
const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
|
|
25
42
|
try {
|
|
26
43
|
process.kill(pid, "SIGTERM");
|
|
27
|
-
console.log(`Sent stop
|
|
44
|
+
console.log(`Sent stop to PID ${pid}.`);
|
|
28
45
|
}
|
|
29
46
|
catch {
|
|
30
|
-
console.log("
|
|
31
|
-
|
|
47
|
+
console.log("Stale lock. Cleaning up.");
|
|
48
|
+
releaseLock();
|
|
32
49
|
}
|
|
33
50
|
}
|
|
34
51
|
export async function daemonStart() {
|
|
52
|
+
if (!acquireLock()) {
|
|
53
|
+
console.error("Daemon already running (lock exists). Use `sofer daemon stop` first.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
35
56
|
const secrets = loadSecrets();
|
|
36
57
|
if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
|
|
37
58
|
console.error("Missing secrets. Run `sofer init` first.");
|
|
38
59
|
process.exit(1);
|
|
39
60
|
}
|
|
61
|
+
console.log("[daemon] bootstrapping…");
|
|
40
62
|
const config = loadConfig();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
let agent;
|
|
64
|
+
try {
|
|
65
|
+
agent = await SoferAgent.create(config, {
|
|
66
|
+
suiSecretKey: secrets.suiSecretKey,
|
|
67
|
+
anthropicApiKey: secrets.anthropicApiKey,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.error("[daemon] failed to create agent:", e.message);
|
|
72
|
+
releaseLock();
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
45
75
|
// SSE clients
|
|
46
76
|
let sseId = 0;
|
|
47
77
|
const sseClients = new Set();
|
|
48
78
|
function broadcast(event, data) {
|
|
79
|
+
const payload = JSON.stringify(data);
|
|
49
80
|
for (const c of sseClients) {
|
|
50
|
-
c.res.write(`event: ${event}\ndata: ${
|
|
81
|
+
c.res.write(`event: ${event}\ndata: ${payload}\n\n`);
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
|
-
// HTTP server
|
|
84
|
+
// ── HTTP server ────────────────────────────────────────────────────────
|
|
54
85
|
const server = createServer(async (req, res) => {
|
|
55
86
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
56
87
|
if (req.method === "OPTIONS") {
|
|
57
88
|
res.writeHead(204).end();
|
|
58
89
|
return;
|
|
59
90
|
}
|
|
91
|
+
const url = req.url ?? "/";
|
|
60
92
|
// GET /healthz
|
|
61
|
-
if (req.
|
|
93
|
+
if (req.method === "GET" && url === "/healthz") {
|
|
62
94
|
res.writeHead(200, { "content-type": "application/json" });
|
|
63
|
-
res.end(JSON.stringify({ status:
|
|
95
|
+
res.end(JSON.stringify({ status: state, agent: agent.address.slice(0, 10), network: config.network }));
|
|
64
96
|
return;
|
|
65
97
|
}
|
|
66
98
|
// GET /events (SSE)
|
|
67
|
-
if (req.url === "/events") {
|
|
99
|
+
if (req.method === "GET" && url === "/events") {
|
|
68
100
|
res.writeHead(200, {
|
|
69
101
|
"content-type": "text/event-stream",
|
|
70
102
|
"cache-control": "no-cache",
|
|
71
|
-
|
|
103
|
+
connection: "keep-alive",
|
|
72
104
|
});
|
|
73
105
|
const client = { id: ++sseId, res };
|
|
74
106
|
sseClients.add(client);
|
|
@@ -77,7 +109,7 @@ export async function daemonStart() {
|
|
|
77
109
|
return;
|
|
78
110
|
}
|
|
79
111
|
// POST /chat
|
|
80
|
-
if (req.method === "POST" &&
|
|
112
|
+
if (req.method === "POST" && url === "/chat") {
|
|
81
113
|
const body = await readBody(req);
|
|
82
114
|
try {
|
|
83
115
|
const { message } = JSON.parse(body);
|
|
@@ -85,23 +117,38 @@ export async function daemonStart() {
|
|
|
85
117
|
res.writeHead(400).end("missing message");
|
|
86
118
|
return;
|
|
87
119
|
}
|
|
88
|
-
broadcast("thinking",
|
|
120
|
+
broadcast("thinking", { status: "thinking" });
|
|
89
121
|
const { text } = await agent.chat(message);
|
|
90
|
-
broadcast("response",
|
|
91
|
-
broadcast("idle",
|
|
122
|
+
broadcast("response", { text });
|
|
123
|
+
broadcast("idle", { status: "idle" });
|
|
92
124
|
res.writeHead(200, { "content-type": "application/json" });
|
|
93
125
|
res.end(JSON.stringify({ text }));
|
|
94
126
|
}
|
|
95
127
|
catch (e) {
|
|
96
|
-
broadcast("error",
|
|
128
|
+
broadcast("error", { error: e.message });
|
|
129
|
+
res.writeHead(500).end(e.message);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// POST /sync — force memory flush
|
|
134
|
+
if (req.method === "POST" && url === "/sync") {
|
|
135
|
+
broadcast("sync", { status: "syncing" });
|
|
136
|
+
// Memory is already persisted via JSONL; trigger a fresh agent state read
|
|
137
|
+
try {
|
|
138
|
+
const bal = await agent.getBalance();
|
|
139
|
+
broadcast("sync", { status: "done", balance: (Number(bal) / 1e9).toFixed(4) });
|
|
140
|
+
res.writeHead(200).end("synced");
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
97
143
|
res.writeHead(500).end(e.message);
|
|
98
144
|
}
|
|
99
145
|
return;
|
|
100
146
|
}
|
|
101
147
|
// GET /status
|
|
102
|
-
if (req.url === "/status") {
|
|
148
|
+
if (req.method === "GET" && url === "/status") {
|
|
103
149
|
res.writeHead(200, { "content-type": "application/json" });
|
|
104
150
|
res.end(JSON.stringify({
|
|
151
|
+
state,
|
|
105
152
|
agent: agent.address,
|
|
106
153
|
name: agent.name,
|
|
107
154
|
network: config.network,
|
|
@@ -112,7 +159,7 @@ export async function daemonStart() {
|
|
|
112
159
|
}
|
|
113
160
|
res.writeHead(404).end("not found");
|
|
114
161
|
});
|
|
115
|
-
// Telegram
|
|
162
|
+
// ── Telegram ───────────────────────────────────────────────────────────
|
|
116
163
|
const tgPath = `${agentPaths.root}/telegram.enc`;
|
|
117
164
|
const tgEnc = readSecret(tgPath);
|
|
118
165
|
let tgListener = null;
|
|
@@ -127,33 +174,53 @@ export async function daemonStart() {
|
|
|
127
174
|
agentName: agent.name.toLowerCase(),
|
|
128
175
|
});
|
|
129
176
|
tgListener.onDispatch(async (input) => {
|
|
130
|
-
broadcast("tg-message",
|
|
177
|
+
broadcast("tg-message", { from: input.displayName, text: input.text.slice(0, 200) });
|
|
131
178
|
const { text } = await agent.chat(input.text);
|
|
132
|
-
broadcast("tg-response",
|
|
179
|
+
broadcast("tg-response", { text: text.slice(0, 200) });
|
|
133
180
|
return text;
|
|
134
181
|
});
|
|
135
182
|
}
|
|
136
183
|
}
|
|
137
184
|
catch { /* skip */ }
|
|
138
185
|
}
|
|
186
|
+
// ── Bind ───────────────────────────────────────────────────────────────
|
|
139
187
|
const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
188
|
+
// Try Unix socket first, fall back to TCP
|
|
189
|
+
try {
|
|
190
|
+
if (existsSync(SOCK_PATH))
|
|
191
|
+
unlinkSync(SOCK_PATH);
|
|
192
|
+
server.listen(SOCK_PATH, () => {
|
|
193
|
+
chmodSync(SOCK_PATH, 0o600);
|
|
194
|
+
state = "ready";
|
|
195
|
+
console.log(`[daemon] ready — unix socket ${SOCK_PATH}`);
|
|
196
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
197
|
+
if (tgListener) {
|
|
198
|
+
console.log("[daemon] telegram: starting bot…");
|
|
199
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
server.listen(PORT, () => {
|
|
205
|
+
state = "ready";
|
|
206
|
+
console.log(`[daemon] ready — tcp port ${PORT} (SOFER_DAEMON_PORT)`);
|
|
207
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
208
|
+
if (tgListener) {
|
|
209
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// ── Shutdown ───────────────────────────────────────────────────────────
|
|
150
214
|
const shutdown = async () => {
|
|
215
|
+
state = "shutting_down";
|
|
151
216
|
console.log("\n[daemon] shutting down…");
|
|
152
217
|
if (tgListener)
|
|
153
|
-
await tgListener.stop();
|
|
218
|
+
await tgListener.stop().catch(() => { });
|
|
154
219
|
server.close();
|
|
220
|
+
releaseLock();
|
|
155
221
|
try {
|
|
156
|
-
|
|
222
|
+
if (existsSync(SOCK_PATH))
|
|
223
|
+
unlinkSync(SOCK_PATH);
|
|
157
224
|
}
|
|
158
225
|
catch { /* */ }
|
|
159
226
|
process.exit(0);
|
package/dist/daemon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/daemon.ts
CHANGED
|
@@ -1,81 +1,111 @@
|
|
|
1
1
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
-
import {
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import {
|
|
5
|
-
type SoferConfig,
|
|
6
5
|
agentPaths,
|
|
7
6
|
decryptWithKey,
|
|
8
7
|
loadConfig,
|
|
9
8
|
readSecret,
|
|
9
|
+
shortAgentId,
|
|
10
10
|
SoferAgent,
|
|
11
11
|
} from "@sofer_agent/core";
|
|
12
12
|
import { TelegramListener } from "@sofer_agent/plugin-telegram";
|
|
13
13
|
import { loadSecrets } from "./env.js";
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const LOCK_FILE = join(agentPaths.root, "daemon.lock");
|
|
16
|
+
const SOCK_PATH = join(agentPaths.root, "daemon.sock");
|
|
16
17
|
|
|
17
|
-
interface SSEClient {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
18
|
+
interface SSEClient { id: number; res: ServerResponse; }
|
|
19
|
+
|
|
20
|
+
// ── Process lock ──────────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
|
-
function
|
|
23
|
-
|
|
22
|
+
function acquireLock(): boolean {
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(LOCK_FILE)) {
|
|
25
|
+
const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
|
|
26
|
+
try { process.kill(pid, 0); return false; } catch { /* stale */ }
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(LOCK_FILE, String(process.pid), "utf8");
|
|
29
|
+
return true;
|
|
30
|
+
} catch { return false; }
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
function
|
|
27
|
-
try {
|
|
33
|
+
function releaseLock(): void {
|
|
34
|
+
try { if (existsSync(LOCK_FILE)) unlinkSync(LOCK_FILE); } catch { /* */ }
|
|
28
35
|
}
|
|
29
36
|
|
|
37
|
+
// ── State machine ─────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
type DaemonState = "bootstrapping" | "ready" | "shutting_down";
|
|
40
|
+
let state: DaemonState = "bootstrapping";
|
|
41
|
+
|
|
42
|
+
// ── Main ──────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
30
44
|
export async function daemonStop(): Promise<void> {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try { process.kill(pid, "SIGTERM"); console.log(`Sent stop
|
|
34
|
-
catch { console.log("
|
|
45
|
+
if (!existsSync(LOCK_FILE)) { console.log("No daemon running."); return; }
|
|
46
|
+
const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
|
|
47
|
+
try { process.kill(pid, "SIGTERM"); console.log(`Sent stop to PID ${pid}.`); }
|
|
48
|
+
catch { console.log("Stale lock. Cleaning up."); releaseLock(); }
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
export async function daemonStart(): Promise<void> {
|
|
52
|
+
if (!acquireLock()) {
|
|
53
|
+
console.error("Daemon already running (lock exists). Use `sofer daemon stop` first.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
const secrets = loadSecrets();
|
|
39
58
|
if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
|
|
40
59
|
console.error("Missing secrets. Run `sofer init` first.");
|
|
41
60
|
process.exit(1);
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
console.log("[daemon] bootstrapping…");
|
|
44
64
|
const config = loadConfig();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
let agent: SoferAgent;
|
|
66
|
+
try {
|
|
67
|
+
agent = await SoferAgent.create(config, {
|
|
68
|
+
suiSecretKey: secrets.suiSecretKey,
|
|
69
|
+
anthropicApiKey: secrets.anthropicApiKey,
|
|
70
|
+
});
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error("[daemon] failed to create agent:", (e as Error).message);
|
|
73
|
+
releaseLock();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
49
76
|
|
|
50
77
|
// SSE clients
|
|
51
78
|
let sseId = 0;
|
|
52
79
|
const sseClients = new Set<SSEClient>();
|
|
53
80
|
|
|
54
|
-
function broadcast(event: string, data: string): void {
|
|
81
|
+
function broadcast(event: string, data: Record<string, unknown>): void {
|
|
82
|
+
const payload = JSON.stringify(data);
|
|
55
83
|
for (const c of sseClients) {
|
|
56
|
-
c.res.write(`event: ${event}\ndata: ${
|
|
84
|
+
c.res.write(`event: ${event}\ndata: ${payload}\n\n`);
|
|
57
85
|
}
|
|
58
86
|
}
|
|
59
87
|
|
|
60
|
-
// HTTP server
|
|
88
|
+
// ── HTTP server ────────────────────────────────────────────────────────
|
|
89
|
+
|
|
61
90
|
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
62
91
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
63
|
-
|
|
64
92
|
if (req.method === "OPTIONS") { res.writeHead(204).end(); return; }
|
|
65
93
|
|
|
94
|
+
const url = req.url ?? "/";
|
|
95
|
+
|
|
66
96
|
// GET /healthz
|
|
67
|
-
if (req.
|
|
97
|
+
if (req.method === "GET" && url === "/healthz") {
|
|
68
98
|
res.writeHead(200, { "content-type": "application/json" });
|
|
69
|
-
res.end(JSON.stringify({ status:
|
|
99
|
+
res.end(JSON.stringify({ status: state, agent: agent.address.slice(0, 10), network: config.network }));
|
|
70
100
|
return;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
103
|
// GET /events (SSE)
|
|
74
|
-
if (req.url === "/events") {
|
|
104
|
+
if (req.method === "GET" && url === "/events") {
|
|
75
105
|
res.writeHead(200, {
|
|
76
106
|
"content-type": "text/event-stream",
|
|
77
107
|
"cache-control": "no-cache",
|
|
78
|
-
|
|
108
|
+
connection: "keep-alive",
|
|
79
109
|
});
|
|
80
110
|
const client: SSEClient = { id: ++sseId, res };
|
|
81
111
|
sseClients.add(client);
|
|
@@ -85,30 +115,43 @@ export async function daemonStart(): Promise<void> {
|
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
// POST /chat
|
|
88
|
-
if (req.method === "POST" &&
|
|
118
|
+
if (req.method === "POST" && url === "/chat") {
|
|
89
119
|
const body = await readBody(req);
|
|
90
120
|
try {
|
|
91
121
|
const { message } = JSON.parse(body);
|
|
92
122
|
if (!message) { res.writeHead(400).end("missing message"); return; }
|
|
93
|
-
|
|
94
|
-
broadcast("thinking", JSON.stringify({ status: "thinking" }));
|
|
123
|
+
broadcast("thinking", { status: "thinking" });
|
|
95
124
|
const { text } = await agent.chat(message);
|
|
96
|
-
broadcast("response",
|
|
97
|
-
broadcast("idle",
|
|
98
|
-
|
|
125
|
+
broadcast("response", { text });
|
|
126
|
+
broadcast("idle", { status: "idle" });
|
|
99
127
|
res.writeHead(200, { "content-type": "application/json" });
|
|
100
128
|
res.end(JSON.stringify({ text }));
|
|
101
129
|
} catch (e) {
|
|
102
|
-
broadcast("error",
|
|
130
|
+
broadcast("error", { error: (e as Error).message });
|
|
131
|
+
res.writeHead(500).end((e as Error).message);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// POST /sync — force memory flush
|
|
137
|
+
if (req.method === "POST" && url === "/sync") {
|
|
138
|
+
broadcast("sync", { status: "syncing" });
|
|
139
|
+
// Memory is already persisted via JSONL; trigger a fresh agent state read
|
|
140
|
+
try {
|
|
141
|
+
const bal = await agent.getBalance();
|
|
142
|
+
broadcast("sync", { status: "done", balance: (Number(bal) / 1e9).toFixed(4) });
|
|
143
|
+
res.writeHead(200).end("synced");
|
|
144
|
+
} catch (e) {
|
|
103
145
|
res.writeHead(500).end((e as Error).message);
|
|
104
146
|
}
|
|
105
147
|
return;
|
|
106
148
|
}
|
|
107
149
|
|
|
108
150
|
// GET /status
|
|
109
|
-
if (req.url === "/status") {
|
|
151
|
+
if (req.method === "GET" && url === "/status") {
|
|
110
152
|
res.writeHead(200, { "content-type": "application/json" });
|
|
111
153
|
res.end(JSON.stringify({
|
|
154
|
+
state,
|
|
112
155
|
agent: agent.address,
|
|
113
156
|
name: agent.name,
|
|
114
157
|
network: config.network,
|
|
@@ -121,7 +164,8 @@ export async function daemonStart(): Promise<void> {
|
|
|
121
164
|
res.writeHead(404).end("not found");
|
|
122
165
|
});
|
|
123
166
|
|
|
124
|
-
// Telegram
|
|
167
|
+
// ── Telegram ───────────────────────────────────────────────────────────
|
|
168
|
+
|
|
125
169
|
const tgPath = `${agentPaths.root}/telegram.enc`;
|
|
126
170
|
const tgEnc = readSecret(tgPath);
|
|
127
171
|
let tgListener: TelegramListener | null = null;
|
|
@@ -136,34 +180,55 @@ export async function daemonStart(): Promise<void> {
|
|
|
136
180
|
agentName: agent.name.toLowerCase(),
|
|
137
181
|
});
|
|
138
182
|
tgListener.onDispatch(async (input) => {
|
|
139
|
-
broadcast("tg-message",
|
|
183
|
+
broadcast("tg-message", { from: input.displayName, text: input.text.slice(0, 200) });
|
|
140
184
|
const { text } = await agent.chat(input.text);
|
|
141
|
-
broadcast("tg-response",
|
|
185
|
+
broadcast("tg-response", { text: text.slice(0, 200) });
|
|
142
186
|
return text;
|
|
143
187
|
});
|
|
144
188
|
}
|
|
145
189
|
} catch { /* skip */ }
|
|
146
190
|
}
|
|
147
191
|
|
|
192
|
+
// ── Bind ───────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
148
194
|
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
195
|
|
|
159
|
-
//
|
|
196
|
+
// Try Unix socket first, fall back to TCP
|
|
197
|
+
try {
|
|
198
|
+
if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH);
|
|
199
|
+
server.listen(SOCK_PATH, () => {
|
|
200
|
+
chmodSync(SOCK_PATH, 0o600);
|
|
201
|
+
state = "ready";
|
|
202
|
+
console.log(`[daemon] ready — unix socket ${SOCK_PATH}`);
|
|
203
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
204
|
+
if (tgListener) {
|
|
205
|
+
console.log("[daemon] telegram: starting bot…");
|
|
206
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
} catch {
|
|
210
|
+
server.listen(PORT, () => {
|
|
211
|
+
state = "ready";
|
|
212
|
+
console.log(`[daemon] ready — tcp port ${PORT} (SOFER_DAEMON_PORT)`);
|
|
213
|
+
console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
|
|
214
|
+
if (tgListener) {
|
|
215
|
+
tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Shutdown ───────────────────────────────────────────────────────────
|
|
221
|
+
|
|
160
222
|
const shutdown = async () => {
|
|
223
|
+
state = "shutting_down";
|
|
161
224
|
console.log("\n[daemon] shutting down…");
|
|
162
|
-
if (tgListener) await tgListener.stop();
|
|
225
|
+
if (tgListener) await tgListener.stop().catch(() => {});
|
|
163
226
|
server.close();
|
|
164
|
-
|
|
227
|
+
releaseLock();
|
|
228
|
+
try { if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH); } catch { /* */ }
|
|
165
229
|
process.exit(0);
|
|
166
230
|
};
|
|
231
|
+
|
|
167
232
|
process.on("SIGINT", shutdown);
|
|
168
233
|
process.on("SIGTERM", shutdown);
|
|
169
234
|
}
|