mesozoic 1.0.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.
Potentially problematic release.
This version of mesozoic might be problematic. Click here for more details.
- package/README.md +38 -0
- package/bin/meso +11 -0
- package/dist/cli/main.js +126854 -0
- package/dist/daemon/supervisor.js +290 -0
- package/dist/dream.js +118602 -0
- package/dist/index.js +123245 -0
- package/dist/list-models.js +118090 -0
- package/dist/scheduler.js +124766 -0
- package/dist/tui.js +122782 -0
- package/guardrails.json +32 -0
- package/package.json +39 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/daemon/supervisor.ts
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { createGzip } from "zlib";
|
|
14
|
+
import { pipeline } from "stream/promises";
|
|
15
|
+
var configJson = process.argv[2];
|
|
16
|
+
if (!configJson) {
|
|
17
|
+
console.error("supervisor: missing config argument");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
var config = JSON.parse(configJson);
|
|
21
|
+
var RESTART_DELAY_BASE = 3e3;
|
|
22
|
+
var RESTART_DELAY_MAX = 6e4;
|
|
23
|
+
var RESTART_BACKOFF = 1.5;
|
|
24
|
+
var MAX_RESTARTS = 50;
|
|
25
|
+
var RESTART_WINDOW = 36e5;
|
|
26
|
+
var LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
27
|
+
var LOG_RETAIN = 7;
|
|
28
|
+
var LOG_CHECK_INTERVAL = 6e4;
|
|
29
|
+
var MEMORY_CHECK_INTERVAL = 3e4;
|
|
30
|
+
var KILL_TIMEOUT = 5e3;
|
|
31
|
+
var children = [];
|
|
32
|
+
var shuttingDown = false;
|
|
33
|
+
function ts() {
|
|
34
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
35
|
+
}
|
|
36
|
+
function log(msg) {
|
|
37
|
+
const line = `${ts()} ${msg}
|
|
38
|
+
`;
|
|
39
|
+
process.stdout.write(line);
|
|
40
|
+
}
|
|
41
|
+
function openLogStream(filePath) {
|
|
42
|
+
return fs.createWriteStream(filePath, { flags: "a" });
|
|
43
|
+
}
|
|
44
|
+
function pipeWithTimestamp(readable, stream) {
|
|
45
|
+
let partial = "";
|
|
46
|
+
readable.on("data", (chunk) => {
|
|
47
|
+
const text = partial + chunk.toString();
|
|
48
|
+
const lines = text.split("\n");
|
|
49
|
+
partial = lines.pop() || "";
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (line) stream.write(`${ts()} ${line}
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
readable.on("end", () => {
|
|
56
|
+
if (partial) stream.write(`${ts()} ${partial}
|
|
57
|
+
`);
|
|
58
|
+
partial = "";
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function rotateLog(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
const stat = fs.statSync(filePath);
|
|
64
|
+
if (stat.size < LOG_MAX_BYTES) return;
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (let i = LOG_RETAIN; i >= 1; i--) {
|
|
69
|
+
const from = i === 1 ? filePath : `${filePath}.${i - 1}`;
|
|
70
|
+
const to = `${filePath}.${i}`;
|
|
71
|
+
try {
|
|
72
|
+
if (i === LOG_RETAIN) {
|
|
73
|
+
fs.unlinkSync(`${filePath}.${LOG_RETAIN}`);
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(`${filePath}.${LOG_RETAIN}.gz`);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (fs.existsSync(from)) fs.renameSync(from, to);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (let i = 2; i <= LOG_RETAIN; i++) {
|
|
84
|
+
const file = `${filePath}.${i}`;
|
|
85
|
+
if (fs.existsSync(file) && !fs.existsSync(`${file}.gz`)) {
|
|
86
|
+
try {
|
|
87
|
+
const src = fs.createReadStream(file);
|
|
88
|
+
const dest = fs.createWriteStream(`${file}.gz`);
|
|
89
|
+
await pipeline(src, createGzip(), dest);
|
|
90
|
+
fs.unlinkSync(file);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function checkLogRotation() {
|
|
97
|
+
for (const child of children) {
|
|
98
|
+
const needsOutRotation = needsRotation(child.outPath);
|
|
99
|
+
const needsErrRotation = needsRotation(child.errPath);
|
|
100
|
+
if (needsOutRotation) {
|
|
101
|
+
child.outStream?.end();
|
|
102
|
+
await rotateLog(child.outPath);
|
|
103
|
+
child.outStream = openLogStream(child.outPath);
|
|
104
|
+
if (child.process?.stdout) pipeWithTimestamp(child.process.stdout, child.outStream);
|
|
105
|
+
}
|
|
106
|
+
if (needsErrRotation) {
|
|
107
|
+
child.errStream?.end();
|
|
108
|
+
await rotateLog(child.errPath);
|
|
109
|
+
child.errStream = openLogStream(child.errPath);
|
|
110
|
+
if (child.process?.stderr) pipeWithTimestamp(child.process.stderr, child.errStream);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function needsRotation(filePath) {
|
|
115
|
+
try {
|
|
116
|
+
return fs.statSync(filePath).size >= LOG_MAX_BYTES;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function checkMemory() {
|
|
122
|
+
for (const child of children) {
|
|
123
|
+
if (!child.config.maxMemoryMB || !child.process || child.status !== "running") continue;
|
|
124
|
+
const pid = child.process.pid;
|
|
125
|
+
if (!pid) continue;
|
|
126
|
+
try {
|
|
127
|
+
const rssKB = parseInt(
|
|
128
|
+
__require("child_process").execSync(`ps -o rss= -p ${pid}`, { encoding: "utf-8" }).trim(),
|
|
129
|
+
10
|
|
130
|
+
);
|
|
131
|
+
const rssMB = rssKB / 1024;
|
|
132
|
+
if (rssMB > child.config.maxMemoryMB) {
|
|
133
|
+
log(`[${child.config.name}] Memory ${Math.round(rssMB)}MB exceeds limit ${child.config.maxMemoryMB}MB, restarting`);
|
|
134
|
+
child.process.kill("SIGTERM");
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function updateStateFile() {
|
|
141
|
+
try {
|
|
142
|
+
let state = {};
|
|
143
|
+
try {
|
|
144
|
+
state = JSON.parse(fs.readFileSync(config.stateFile, "utf-8"));
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
if (!state.agents) state.agents = {};
|
|
148
|
+
state.agents[config.agentId] = {
|
|
149
|
+
supervisorPid: process.pid,
|
|
150
|
+
startedAt,
|
|
151
|
+
children: Object.fromEntries(
|
|
152
|
+
children.map((c) => [
|
|
153
|
+
c.config.name.replace(`meso-${config.agentId}`, "").replace(/^-/, "") || "channel",
|
|
154
|
+
{
|
|
155
|
+
pid: c.pid,
|
|
156
|
+
name: c.config.name,
|
|
157
|
+
restartCount: c.restartCount,
|
|
158
|
+
status: c.status,
|
|
159
|
+
startedAt: c.lastRestartAt ? new Date(c.lastRestartAt).toISOString() : startedAt
|
|
160
|
+
}
|
|
161
|
+
])
|
|
162
|
+
)
|
|
163
|
+
};
|
|
164
|
+
const tmp = config.stateFile + ".tmp";
|
|
165
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
166
|
+
fs.renameSync(tmp, config.stateFile);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
171
|
+
function getRestartDelay(restartCount) {
|
|
172
|
+
return Math.min(RESTART_DELAY_BASE * Math.pow(RESTART_BACKOFF, restartCount), RESTART_DELAY_MAX);
|
|
173
|
+
}
|
|
174
|
+
function spawnChild(state) {
|
|
175
|
+
if (shuttingDown) return;
|
|
176
|
+
state.outStream = openLogStream(state.outPath);
|
|
177
|
+
state.errStream = openLogStream(state.errPath);
|
|
178
|
+
const child = spawn("node", [state.config.script, ...state.config.args], {
|
|
179
|
+
cwd: config.cwd,
|
|
180
|
+
env: { ...process.env, ...config.env },
|
|
181
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
182
|
+
});
|
|
183
|
+
state.process = child;
|
|
184
|
+
state.pid = child.pid || 0;
|
|
185
|
+
state.status = "running";
|
|
186
|
+
if (child.stdout) pipeWithTimestamp(child.stdout, state.outStream);
|
|
187
|
+
if (child.stderr) pipeWithTimestamp(child.stderr, state.errStream);
|
|
188
|
+
log(`[${state.config.name}] Started pid=${state.pid}`);
|
|
189
|
+
updateStateFile();
|
|
190
|
+
child.on("exit", (code, signal) => {
|
|
191
|
+
state.status = "stopped";
|
|
192
|
+
state.outStream?.end();
|
|
193
|
+
state.errStream?.end();
|
|
194
|
+
log(`[${state.config.name}] Exited code=${code} signal=${signal}`);
|
|
195
|
+
if (shuttingDown) {
|
|
196
|
+
updateStateFile();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
if (state.firstRestartAt && now - state.firstRestartAt > RESTART_WINDOW) {
|
|
201
|
+
state.restartCount = 0;
|
|
202
|
+
state.firstRestartAt = 0;
|
|
203
|
+
}
|
|
204
|
+
if (state.restartCount >= MAX_RESTARTS) {
|
|
205
|
+
log(`[${state.config.name}] Max restarts (${MAX_RESTARTS}) reached, giving up`);
|
|
206
|
+
state.status = "errored";
|
|
207
|
+
updateStateFile();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
state.restartCount++;
|
|
211
|
+
if (!state.firstRestartAt) state.firstRestartAt = now;
|
|
212
|
+
state.lastRestartAt = now;
|
|
213
|
+
const delay = getRestartDelay(state.restartCount - 1);
|
|
214
|
+
log(`[${state.config.name}] Restarting in ${delay}ms (attempt ${state.restartCount}/${MAX_RESTARTS})`);
|
|
215
|
+
updateStateFile();
|
|
216
|
+
setTimeout(() => spawnChild(state), delay);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function shutdown(signal) {
|
|
220
|
+
if (shuttingDown) return;
|
|
221
|
+
shuttingDown = true;
|
|
222
|
+
log(`Supervisor received ${signal}, shutting down...`);
|
|
223
|
+
const alive = children.filter((c) => c.process && c.status === "running");
|
|
224
|
+
for (const child of alive) {
|
|
225
|
+
try {
|
|
226
|
+
child.process.kill("SIGTERM");
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const killTimer = setTimeout(() => {
|
|
231
|
+
for (const child of alive) {
|
|
232
|
+
try {
|
|
233
|
+
child.process.kill("SIGKILL");
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
cleanup();
|
|
238
|
+
}, KILL_TIMEOUT);
|
|
239
|
+
let exited = 0;
|
|
240
|
+
for (const child of alive) {
|
|
241
|
+
child.process.on("exit", () => {
|
|
242
|
+
exited++;
|
|
243
|
+
if (exited >= alive.length) {
|
|
244
|
+
clearTimeout(killTimer);
|
|
245
|
+
cleanup();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (alive.length === 0) {
|
|
250
|
+
clearTimeout(killTimer);
|
|
251
|
+
cleanup();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function cleanup() {
|
|
255
|
+
try {
|
|
256
|
+
const state = JSON.parse(fs.readFileSync(config.stateFile, "utf-8"));
|
|
257
|
+
delete state.agents?.[config.agentId];
|
|
258
|
+
const tmp = config.stateFile + ".tmp";
|
|
259
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
260
|
+
fs.renameSync(tmp, config.stateFile);
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
log("Supervisor exiting.");
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
267
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
268
|
+
fs.mkdirSync(config.logsDir, { recursive: true });
|
|
269
|
+
for (const childConfig of config.children) {
|
|
270
|
+
const role = childConfig.name.replace(`meso-${config.agentId}`, "").replace(/^-/, "") || "channel";
|
|
271
|
+
const state = {
|
|
272
|
+
config: childConfig,
|
|
273
|
+
process: null,
|
|
274
|
+
pid: 0,
|
|
275
|
+
restartCount: 0,
|
|
276
|
+
lastRestartAt: 0,
|
|
277
|
+
firstRestartAt: 0,
|
|
278
|
+
status: "stopped",
|
|
279
|
+
outPath: path.join(config.logsDir, `${role}-out.log`),
|
|
280
|
+
errPath: path.join(config.logsDir, `${role}-err.log`),
|
|
281
|
+
outStream: null,
|
|
282
|
+
errStream: null
|
|
283
|
+
};
|
|
284
|
+
children.push(state);
|
|
285
|
+
spawnChild(state);
|
|
286
|
+
}
|
|
287
|
+
setInterval(() => checkLogRotation().catch(() => {
|
|
288
|
+
}), LOG_CHECK_INTERVAL);
|
|
289
|
+
setInterval(checkMemory, MEMORY_CHECK_INTERVAL);
|
|
290
|
+
log(`Supervisor started for agent=${config.agentId} pid=${process.pid} children=${children.length}`);
|