doer-agent 0.4.8 → 0.4.9
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/agent-daemon-rpc.js
CHANGED
|
@@ -105,7 +105,13 @@ function normalizeLogEvent(value) {
|
|
|
105
105
|
}
|
|
106
106
|
const row = value;
|
|
107
107
|
const ts = typeof row.ts === "string" ? row.ts.trim() : "";
|
|
108
|
-
const type = row.type === "start" ||
|
|
108
|
+
const type = row.type === "start" ||
|
|
109
|
+
row.type === "stdout" ||
|
|
110
|
+
row.type === "stderr" ||
|
|
111
|
+
row.type === "heartbeat" ||
|
|
112
|
+
row.type === "exit" ||
|
|
113
|
+
row.type === "signal" ||
|
|
114
|
+
row.type === "error"
|
|
109
115
|
? row.type
|
|
110
116
|
: null;
|
|
111
117
|
if (!ts || !type) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
const DEFAULT_HEARTBEAT_MS = 15_000;
|
|
3
4
|
function readRequiredEnv(name) {
|
|
4
5
|
const value = process.env[name]?.trim() || "";
|
|
5
6
|
if (!value) {
|
|
@@ -7,6 +8,17 @@ function readRequiredEnv(name) {
|
|
|
7
8
|
}
|
|
8
9
|
return value;
|
|
9
10
|
}
|
|
11
|
+
function readHeartbeatIntervalMs() {
|
|
12
|
+
const raw = process.env.DOER_DAEMON_HEARTBEAT_MS?.trim();
|
|
13
|
+
if (!raw) {
|
|
14
|
+
return DEFAULT_HEARTBEAT_MS;
|
|
15
|
+
}
|
|
16
|
+
const numeric = Number(raw);
|
|
17
|
+
if (!Number.isFinite(numeric) || numeric < 1_000) {
|
|
18
|
+
return DEFAULT_HEARTBEAT_MS;
|
|
19
|
+
}
|
|
20
|
+
return Math.floor(numeric);
|
|
21
|
+
}
|
|
10
22
|
async function readState(statePath) {
|
|
11
23
|
const raw = await readFile(statePath, "utf8");
|
|
12
24
|
return JSON.parse(raw);
|
|
@@ -25,13 +37,14 @@ async function appendEvent(eventsPath, event) {
|
|
|
25
37
|
};
|
|
26
38
|
await appendFile(eventsPath, `${JSON.stringify(row)}\n`, "utf8");
|
|
27
39
|
}
|
|
28
|
-
function attachLineLogger(stream, type, eventsPath, pid) {
|
|
40
|
+
function attachLineLogger(stream, type, eventsPath, pid, onActivity) {
|
|
29
41
|
if (!stream) {
|
|
30
42
|
return;
|
|
31
43
|
}
|
|
32
44
|
stream.setEncoding("utf8");
|
|
33
45
|
let pending = "";
|
|
34
46
|
stream.on("data", (chunk) => {
|
|
47
|
+
onActivity?.();
|
|
35
48
|
pending += chunk;
|
|
36
49
|
const lines = pending.split(/\r\n|\n|\r/);
|
|
37
50
|
pending = lines.pop() ?? "";
|
|
@@ -61,6 +74,7 @@ async function main() {
|
|
|
61
74
|
const command = readRequiredEnv("DOER_DAEMON_COMMAND");
|
|
62
75
|
const cwd = readRequiredEnv("DOER_DAEMON_CWD");
|
|
63
76
|
const shellPath = readRequiredEnv("DOER_DAEMON_SHELL_PATH");
|
|
77
|
+
const heartbeatIntervalMs = readHeartbeatIntervalMs();
|
|
64
78
|
const childEnv = { ...process.env };
|
|
65
79
|
delete childEnv.DOER_DAEMON_STATE_PATH;
|
|
66
80
|
delete childEnv.DOER_DAEMON_EVENTS_PATH;
|
|
@@ -94,8 +108,28 @@ async function main() {
|
|
|
94
108
|
type: "start",
|
|
95
109
|
pid: child.pid,
|
|
96
110
|
});
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
let lastActivityAt = Date.now();
|
|
112
|
+
const markActivity = () => {
|
|
113
|
+
lastActivityAt = Date.now();
|
|
114
|
+
};
|
|
115
|
+
attachLineLogger(child.stdout, "stdout", eventsPath, child.pid, markActivity);
|
|
116
|
+
attachLineLogger(child.stderr, "stderr", eventsPath, child.pid, markActivity);
|
|
117
|
+
const heartbeatTimer = setInterval(() => {
|
|
118
|
+
if (child.exitCode !== null || child.killed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const idleMs = Date.now() - lastActivityAt;
|
|
122
|
+
if (idleMs < heartbeatIntervalMs) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
lastActivityAt = Date.now();
|
|
126
|
+
void appendEvent(eventsPath, {
|
|
127
|
+
type: "heartbeat",
|
|
128
|
+
pid: child.pid,
|
|
129
|
+
text: `[doer-daemon] heartbeat: process still running without new output for ${Math.max(1, Math.round(idleMs / 1000))}s`,
|
|
130
|
+
});
|
|
131
|
+
}, heartbeatIntervalMs);
|
|
132
|
+
heartbeatTimer.unref?.();
|
|
99
133
|
const forwardSignal = (signal) => {
|
|
100
134
|
if (child.exitCode !== null || child.killed) {
|
|
101
135
|
return;
|
|
@@ -122,6 +156,7 @@ async function main() {
|
|
|
122
156
|
child.once("error", reject);
|
|
123
157
|
child.once("exit", async (code, signal) => {
|
|
124
158
|
try {
|
|
159
|
+
clearInterval(heartbeatTimer);
|
|
125
160
|
const latest = await readState(statePath);
|
|
126
161
|
await writeState(statePath, {
|
|
127
162
|
...latest,
|
|
@@ -140,6 +175,7 @@ async function main() {
|
|
|
140
175
|
resolve();
|
|
141
176
|
}
|
|
142
177
|
catch (error) {
|
|
178
|
+
clearInterval(heartbeatTimer);
|
|
143
179
|
reject(error);
|
|
144
180
|
}
|
|
145
181
|
});
|