@mono-agent/agent-app 0.2.2 → 0.4.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/README.md +9 -5
- package/dist/adapter-send-tools-main.d.ts +3 -0
- package/dist/adapter-send-tools-main.d.ts.map +1 -0
- package/dist/adapter-send-tools-main.js +20 -0
- package/dist/adapter-send-tools-main.js.map +1 -0
- package/dist/adapter-send-tools.d.ts +59 -0
- package/dist/adapter-send-tools.d.ts.map +1 -0
- package/dist/adapter-send-tools.js +254 -0
- package/dist/adapter-send-tools.js.map +1 -0
- package/dist/app-config.d.ts +40 -17
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +188 -68
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +41 -20
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +349 -87
- package/dist/app.js.map +1 -1
- package/dist/backfill.d.ts +79 -0
- package/dist/backfill.d.ts.map +1 -0
- package/dist/backfill.js +246 -0
- package/dist/backfill.js.map +1 -0
- package/dist/background.d.ts +0 -4
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +78 -50
- package/dist/background.js.map +1 -1
- package/dist/channels.d.ts +7 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +26 -3
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +53 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +317 -113
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +236 -30
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/launchd.d.ts +0 -2
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +0 -2
- package/dist/launchd.js.map +1 -1
- package/dist/memory-recall-main.d.ts +3 -0
- package/dist/memory-recall-main.d.ts.map +1 -0
- package/dist/memory-recall-main.js +30 -0
- package/dist/memory-recall-main.js.map +1 -0
- package/dist/memory-recall.d.ts +111 -0
- package/dist/memory-recall.d.ts.map +1 -0
- package/dist/memory-recall.js +275 -0
- package/dist/memory-recall.js.map +1 -0
- package/dist/memory-rituals.d.ts +51 -0
- package/dist/memory-rituals.d.ts.map +1 -0
- package/dist/memory-rituals.js +228 -0
- package/dist/memory-rituals.js.map +1 -0
- package/dist/sessions.d.ts +21 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +45 -0
- package/dist/sessions.js.map +1 -0
- package/dist/ui.d.ts +54 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +153 -0
- package/dist/ui.js.map +1 -0
- package/package.json +22 -17
- package/skills/mono-agent-composer/SKILL.md +7 -6
- package/skills/mono-agent-composer/references/config-blueprint.md +53 -22
- package/skills/mono-agent-composer/references/discovery-questions.md +121 -10
- package/skills/mono-agent-composer/references/feature-coverage.md +15 -12
- package/skills/mono-agent-composer/references/package-map.md +9 -11
- package/skills/mono-agent-composer/references/validation.md +5 -4
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
2
3
|
import { basename, resolve } from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { startMonoAgentApp } from "./app.js";
|
|
7
|
+
import { phoenixAppBaseUrl } from "./app-config.js";
|
|
8
|
+
import { runBackfill } from "./backfill.js";
|
|
6
9
|
import { defaultBackgroundDeps, resolveInstanceTarget, restartBackground, startBackground, statusBackground, stopBackground, tailLogs, } from "./background.js";
|
|
7
10
|
import { validateMonoAgentFolder } from "./doctor.js";
|
|
8
11
|
import { initMonoAgentFolder } from "./init.js";
|
|
9
12
|
import { installComposerSkill } from "./install-skill.js";
|
|
13
|
+
import { purgeSessions } from "./sessions.js";
|
|
14
|
+
import * as ui from "./ui.js";
|
|
10
15
|
const DEFAULT_LOG_LINES = 200;
|
|
16
|
+
// Node's maximum setInterval/setTimeout delay (2^31 - 1 ms, ~24.8 days). A
|
|
17
|
+
// referenced timer at this delay keeps the foreground event loop alive without
|
|
18
|
+
// busy-waiting; larger values silently overflow to a 1ms delay.
|
|
19
|
+
const KEEP_ALIVE_INTERVAL_MS = 2_147_483_647;
|
|
11
20
|
const BACKGROUND_COMMANDS = ["start", "restart", "stop", "status", "logs"];
|
|
12
|
-
const KNOWN_COMMANDS = ["init", "validate", "start", "restart", "stop", "status", "logs", "install-skill"];
|
|
21
|
+
const KNOWN_COMMANDS = ["init", "validate", "start", "restart", "stop", "status", "logs", "install-skill", "backfill"];
|
|
13
22
|
export function parseCliArgs(argv) {
|
|
14
23
|
const [command, ...rest] = argv;
|
|
15
24
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
16
|
-
return { command: "help",
|
|
25
|
+
return { command: "help", force: false, foreground: false, follow: false, all: false, dryRun: false };
|
|
17
26
|
}
|
|
18
27
|
if (!KNOWN_COMMANDS.includes(command)) {
|
|
19
28
|
throw new Error(`Unknown command \`${command}\`. Expected ${KNOWN_COMMANDS.join(", ")}.`);
|
|
@@ -21,32 +30,41 @@ export function parseCliArgs(argv) {
|
|
|
21
30
|
const cmd = command;
|
|
22
31
|
const isLogs = cmd === "logs";
|
|
23
32
|
let configPath;
|
|
24
|
-
let port;
|
|
25
33
|
let model;
|
|
26
34
|
let fallbackModels;
|
|
27
35
|
let memory;
|
|
28
36
|
let envFile;
|
|
29
37
|
let target;
|
|
30
|
-
let noConsole = false;
|
|
31
38
|
let force = false;
|
|
32
39
|
let foreground = false;
|
|
33
40
|
let follow = false;
|
|
34
41
|
let lines;
|
|
42
|
+
let run;
|
|
43
|
+
let all = false;
|
|
44
|
+
let since;
|
|
45
|
+
let until;
|
|
46
|
+
let dryRun = false;
|
|
35
47
|
for (let i = 0; i < rest.length; i += 1) {
|
|
36
48
|
const flag = rest[i];
|
|
37
49
|
switch (flag) {
|
|
38
50
|
case "--config":
|
|
39
51
|
configPath = requireValue(rest, ++i, flag);
|
|
40
52
|
break;
|
|
41
|
-
case "--
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
case "--run":
|
|
54
|
+
run = requireValue(rest, ++i, flag);
|
|
55
|
+
break;
|
|
56
|
+
case "--all":
|
|
57
|
+
all = true;
|
|
58
|
+
break;
|
|
59
|
+
case "--since":
|
|
60
|
+
since = requireValue(rest, ++i, flag);
|
|
61
|
+
break;
|
|
62
|
+
case "--until":
|
|
63
|
+
until = requireValue(rest, ++i, flag);
|
|
64
|
+
break;
|
|
65
|
+
case "--dry-run":
|
|
66
|
+
dryRun = true;
|
|
48
67
|
break;
|
|
49
|
-
}
|
|
50
68
|
case "--model":
|
|
51
69
|
model = requireValue(rest, ++i, flag);
|
|
52
70
|
break;
|
|
@@ -58,8 +76,8 @@ export function parseCliArgs(argv) {
|
|
|
58
76
|
break;
|
|
59
77
|
case "--memory": {
|
|
60
78
|
const raw = requireValue(rest, ++i, flag);
|
|
61
|
-
if (raw !== "
|
|
62
|
-
throw new Error("--memory must be
|
|
79
|
+
if (raw !== "lite" && raw !== "journal" && raw !== "bujo") {
|
|
80
|
+
throw new Error("--memory must be lite, journal, or bujo.");
|
|
63
81
|
}
|
|
64
82
|
memory = raw;
|
|
65
83
|
break;
|
|
@@ -78,9 +96,6 @@ export function parseCliArgs(argv) {
|
|
|
78
96
|
case "--force":
|
|
79
97
|
force = true;
|
|
80
98
|
break;
|
|
81
|
-
case "--no-console":
|
|
82
|
-
noConsole = true;
|
|
83
|
-
break;
|
|
84
99
|
case "--foreground":
|
|
85
100
|
foreground = true;
|
|
86
101
|
break;
|
|
@@ -112,17 +127,20 @@ export function parseCliArgs(argv) {
|
|
|
112
127
|
return {
|
|
113
128
|
command: cmd,
|
|
114
129
|
...(configPath === undefined ? {} : { configPath }),
|
|
115
|
-
...(port === undefined ? {} : { port }),
|
|
116
130
|
...(model === undefined ? {} : { model }),
|
|
117
131
|
...(fallbackModels === undefined ? {} : { fallbackModels }),
|
|
118
132
|
...(memory === undefined ? {} : { memory }),
|
|
119
133
|
...(envFile === undefined ? {} : { envFile }),
|
|
120
134
|
...(target === undefined ? {} : { target }),
|
|
121
|
-
noConsole,
|
|
122
135
|
force,
|
|
123
136
|
foreground,
|
|
124
137
|
follow,
|
|
125
138
|
...(lines === undefined ? {} : { lines }),
|
|
139
|
+
...(run === undefined ? {} : { run }),
|
|
140
|
+
all,
|
|
141
|
+
...(since === undefined ? {} : { since }),
|
|
142
|
+
...(until === undefined ? {} : { until }),
|
|
143
|
+
dryRun,
|
|
126
144
|
};
|
|
127
145
|
}
|
|
128
146
|
/**
|
|
@@ -146,38 +164,66 @@ function requireValue(args, index, flag) {
|
|
|
146
164
|
}
|
|
147
165
|
return value;
|
|
148
166
|
}
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
167
|
+
const HELP_COMMANDS = [
|
|
168
|
+
{
|
|
169
|
+
signature: "mono-agent init [--model <ref>] [--fallback-models <csv>] [--memory lite|journal|bujo]",
|
|
170
|
+
lines: [
|
|
171
|
+
"Scaffold mono-agent.config.json, IDENTITY.md, and .mono-agent/ in the",
|
|
172
|
+
"current folder. Existing files are never overwritten.",
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
signature: "mono-agent validate [--config <path>] [--env-file <path>]",
|
|
177
|
+
lines: ["Load every config section and report what would run, wait, or fail."],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
signature: "mono-agent start [--config <path>] [--env-file <path>] [--foreground|-f]",
|
|
181
|
+
lines: [
|
|
182
|
+
"Start the agent as a background macOS service (launchd), print its",
|
|
183
|
+
"instance info, and return. Re-running restarts the running instance.",
|
|
184
|
+
"Refuses to start without a valid mono-agent.config.json in the folder.",
|
|
185
|
+
"Use --foreground (-f) to run in the blocking foreground instead.",
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
signature: "mono-agent restart [--config <path>] [--force]",
|
|
190
|
+
lines: [
|
|
191
|
+
"Restart the background instance for this config (starts it if stopped).",
|
|
192
|
+
"--force also clears the persisted pi sessions so it starts with fresh",
|
|
193
|
+
"conversations instead of resuming saved ones (durable memory is untouched).",
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
signature: "mono-agent stop [--config <path>]",
|
|
198
|
+
lines: ["Stop the background instance and remove its LaunchAgent."],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
signature: "mono-agent status [--config <path>]",
|
|
202
|
+
lines: ["Show this config's instance plus any other running instances."],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
signature: "mono-agent logs [--config <path>] [--follow|-f] [--lines <n>]",
|
|
206
|
+
lines: ["Print (and optionally follow) the background instance's log files."],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
signature: "mono-agent install-skill [--target claude|codex|both] [--force]",
|
|
210
|
+
lines: [
|
|
211
|
+
"Copy the bundled mono-agent-composer skill into ~/.claude/skills and",
|
|
212
|
+
"~/.codex/skills (default: both). Refuses to overwrite without --force.",
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
signature: "mono-agent backfill (--run <id> | --all) [--since <iso>] [--until <iso>]\n" +
|
|
217
|
+
" [--dry-run] [--config <path>] [--env-file <path>]",
|
|
218
|
+
lines: [
|
|
219
|
+
"Export already-recorded run artifacts to the configured Phoenix exporter",
|
|
220
|
+
"with their historical timestamps. Trace ids are deterministic per run, so",
|
|
221
|
+
"re-running overwrites rather than duplicating. --dry-run maps and",
|
|
222
|
+
"serializes without sending.",
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
const HELP_NOTES = `Background mode runs the agent under launchd, keeping it alive across logins
|
|
181
227
|
(auto-restarting only on crash) until you run stop. Secrets are read from the
|
|
182
228
|
.env file in the working directory, the same as foreground mode. The background
|
|
183
229
|
commands require macOS; elsewhere use start --foreground.
|
|
@@ -188,19 +234,38 @@ pi:<provider>:<model> (e.g. pi:ollama:gemma4:31b).
|
|
|
188
234
|
A .env file in the current folder is loaded automatically when present;
|
|
189
235
|
already-exported shell variables take precedence.
|
|
190
236
|
`;
|
|
237
|
+
/** Build the colorized help screen (plain text when color is disabled). */
|
|
238
|
+
export function renderHelp() {
|
|
239
|
+
let out = ui.banner("mono-agent", "config-first agent host") + "\n";
|
|
240
|
+
out += ui.heading("Usage");
|
|
241
|
+
for (const entry of HELP_COMMANDS) {
|
|
242
|
+
const [first, ...rest] = entry.signature.split("\n");
|
|
243
|
+
out += ` ${ui.style.bold(ui.style.cyan(first ?? ""))}\n`;
|
|
244
|
+
for (const cont of rest) {
|
|
245
|
+
out += ` ${ui.style.cyan(cont)}\n`;
|
|
246
|
+
}
|
|
247
|
+
for (const line of entry.lines) {
|
|
248
|
+
out += ` ${ui.style.dim(line)}\n`;
|
|
249
|
+
}
|
|
250
|
+
out += "\n";
|
|
251
|
+
}
|
|
252
|
+
out += ui.style.dim(HELP_NOTES);
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
191
255
|
export async function runCli(argv) {
|
|
192
256
|
let args;
|
|
193
257
|
try {
|
|
194
258
|
args = parseCliArgs(argv);
|
|
195
259
|
}
|
|
196
260
|
catch (error) {
|
|
197
|
-
process.stderr.write(
|
|
261
|
+
process.stderr.write(ui.errorLine(error instanceof Error ? error.message : String(error)));
|
|
262
|
+
process.stdout.write(`\n${renderHelp()}`);
|
|
198
263
|
return 2;
|
|
199
264
|
}
|
|
200
265
|
loadCliEnvFile(resolve(process.cwd(), args.envFile ?? ".env"));
|
|
201
266
|
switch (args.command) {
|
|
202
267
|
case "help":
|
|
203
|
-
process.stdout.write(
|
|
268
|
+
process.stdout.write(renderHelp());
|
|
204
269
|
return 0;
|
|
205
270
|
case "init":
|
|
206
271
|
return await runInit(args);
|
|
@@ -215,6 +280,15 @@ export async function runCli(argv) {
|
|
|
215
280
|
return await runBackgroundCommand(args, args.command);
|
|
216
281
|
case "install-skill":
|
|
217
282
|
return await runInstallSkill(args);
|
|
283
|
+
case "backfill":
|
|
284
|
+
return await runBackfill({
|
|
285
|
+
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
286
|
+
...(args.run === undefined ? {} : { run: args.run }),
|
|
287
|
+
all: args.all,
|
|
288
|
+
...(args.since === undefined ? {} : { since: args.since }),
|
|
289
|
+
...(args.until === undefined ? {} : { until: args.until }),
|
|
290
|
+
dryRun: args.dryRun,
|
|
291
|
+
});
|
|
218
292
|
}
|
|
219
293
|
}
|
|
220
294
|
async function runInit(args) {
|
|
@@ -225,18 +299,19 @@ async function runInit(args) {
|
|
|
225
299
|
...(args.memory === undefined ? {} : { memory: args.memory }),
|
|
226
300
|
});
|
|
227
301
|
for (const path of result.created) {
|
|
228
|
-
process.stdout.write(
|
|
302
|
+
process.stdout.write(`${ui.badge("ok")}${ui.style.green("created")} ${path}\n`);
|
|
229
303
|
}
|
|
230
304
|
for (const path of result.skipped) {
|
|
231
|
-
process.stdout.write(`kept ${path}\n
|
|
305
|
+
process.stdout.write(ui.style.dim(` kept ${path}`) + "\n");
|
|
232
306
|
}
|
|
233
307
|
if (result.knowledgeFiles.length > 0) {
|
|
234
|
-
process.stdout.write(`\nIdentity references existing knowledge: ${result.knowledgeFiles.join(", ")}\n`);
|
|
308
|
+
process.stdout.write(`\nIdentity references existing knowledge: ${ui.style.cyan(result.knowledgeFiles.join(", "))}\n`);
|
|
235
309
|
}
|
|
236
|
-
process.stdout.write("\
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
310
|
+
process.stdout.write("\n" +
|
|
311
|
+
ui.heading("Next steps") +
|
|
312
|
+
` ${ui.style.bold("1.")} Edit ${result.configPath} ${ui.style.dim("(model, channels, skills, memory, sandbox)")}\n` +
|
|
313
|
+
` ${ui.style.bold("2.")} mono-agent validate\n` +
|
|
314
|
+
` ${ui.style.bold("3.")} mono-agent start\n`);
|
|
240
315
|
return 0;
|
|
241
316
|
}
|
|
242
317
|
async function runInstallSkill(args) {
|
|
@@ -248,11 +323,11 @@ async function runInstallSkill(args) {
|
|
|
248
323
|
});
|
|
249
324
|
}
|
|
250
325
|
catch (error) {
|
|
251
|
-
process.stderr.write(
|
|
326
|
+
process.stderr.write(ui.errorLine(error instanceof Error ? error.message : String(error)));
|
|
252
327
|
return 1;
|
|
253
328
|
}
|
|
254
329
|
for (const path of result.installed) {
|
|
255
|
-
process.stdout.write(
|
|
330
|
+
process.stdout.write(`${ui.badge("ok")}${ui.style.green("installed")} ${path}\n`);
|
|
256
331
|
}
|
|
257
332
|
return 0;
|
|
258
333
|
}
|
|
@@ -264,24 +339,72 @@ async function runValidate(args) {
|
|
|
264
339
|
configPath: resolve(cwd, args.configPath ?? "mono-agent.config.json"),
|
|
265
340
|
});
|
|
266
341
|
for (const section of report.sections) {
|
|
267
|
-
process.stdout.write(
|
|
268
|
-
for (const detail of section.details) {
|
|
269
|
-
process.stdout.write(` ${detail}\n`);
|
|
270
|
-
}
|
|
342
|
+
process.stdout.write(formatSection(section));
|
|
271
343
|
}
|
|
272
|
-
process.stdout.write(report.ok
|
|
344
|
+
process.stdout.write(report.ok
|
|
345
|
+
? `\n${ui.style.green("✓ Config is ready to start.")}\n`
|
|
346
|
+
: `\n${ui.hint("Fix the errors above, then re-run mono-agent validate.")}`);
|
|
273
347
|
return report.ok ? 0 : 1;
|
|
274
348
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
349
|
+
/** Render one validation section: a status badge, a bold label, and its details. */
|
|
350
|
+
function formatSection(section) {
|
|
351
|
+
let out = `${ui.badge(section.status)}${ui.style.bold(section.label)}\n`;
|
|
352
|
+
for (const detail of section.details) {
|
|
353
|
+
out += ` ${colorDetail(section.status, detail)}\n`;
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function colorDetail(status, detail) {
|
|
358
|
+
if (status === "error") {
|
|
359
|
+
return ui.style.red(detail);
|
|
360
|
+
}
|
|
361
|
+
if (detail.startsWith("[WARN]")) {
|
|
362
|
+
return ui.style.yellow(detail);
|
|
363
|
+
}
|
|
364
|
+
return ui.style.dim(detail);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Gate for `start`/`restart`: refuse unless the directory has a present, valid
|
|
368
|
+
* config. First the config FILE must exist (env vars alone are not enough — a
|
|
369
|
+
* folder without a config is not a configured agent). Then run the structural
|
|
370
|
+
* validation with `liveness:false` (network probes only yield `waiting`, never
|
|
371
|
+
* `error`, so skipping them keeps the verdict but avoids ~6s of timeouts) and
|
|
372
|
+
* refuse on any `error` section. `waiting` (e.g. Ollama/Phoenix not up yet) is
|
|
373
|
+
* runtime-soft and never blocks.
|
|
374
|
+
*/
|
|
375
|
+
export async function ensureStartable(args) {
|
|
376
|
+
const cwd = process.cwd();
|
|
377
|
+
const configPath = resolve(cwd, args.configPath ?? "mono-agent.config.json");
|
|
378
|
+
if (!(await pathExists(configPath))) {
|
|
379
|
+
return { ok: false, code: 2, kind: "missing-config", configPath };
|
|
380
|
+
}
|
|
381
|
+
const report = await validateMonoAgentFolder({ env: process.env, cwd, configPath, liveness: false });
|
|
382
|
+
if (!report.ok) {
|
|
383
|
+
return { ok: false, code: 1, kind: "validation", report };
|
|
384
|
+
}
|
|
385
|
+
return { ok: true };
|
|
386
|
+
}
|
|
387
|
+
function printPreflightFailure(result) {
|
|
388
|
+
if (result.kind === "missing-config") {
|
|
389
|
+
process.stderr.write(ui.errorLine(`No mono-agent config found at ${result.configPath}.`));
|
|
390
|
+
process.stderr.write(ui.hint("Run `mono-agent init` to scaffold one, or pass --config <path>."));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
process.stderr.write(ui.heading("Cannot start: config has errors"));
|
|
394
|
+
for (const section of result.report.sections) {
|
|
395
|
+
if (section.status === "error") {
|
|
396
|
+
process.stderr.write(formatSection(section));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
process.stderr.write(ui.hint("Run `mono-agent validate` for the full report, fix the errors, then retry."));
|
|
400
|
+
}
|
|
401
|
+
async function pathExists(path) {
|
|
402
|
+
try {
|
|
403
|
+
await stat(path);
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return false;
|
|
285
408
|
}
|
|
286
409
|
}
|
|
287
410
|
async function runStart(args) {
|
|
@@ -292,33 +415,47 @@ async function runStart(args) {
|
|
|
292
415
|
}
|
|
293
416
|
/**
|
|
294
417
|
* The blocking worker: builds the responder, starts every configured channel
|
|
295
|
-
* plus
|
|
296
|
-
*
|
|
297
|
-
* with `--foreground`/`-f`.
|
|
418
|
+
* plus traceability, and stays alive until a signal. This is what launchd
|
|
419
|
+
* invokes (via `start --foreground`) and what users get with `--foreground`/`-f`.
|
|
298
420
|
*/
|
|
299
421
|
async function runForeground(args) {
|
|
422
|
+
const pre = await ensureStartable(args);
|
|
423
|
+
if (!pre.ok) {
|
|
424
|
+
printPreflightFailure(pre);
|
|
425
|
+
return pre.code;
|
|
426
|
+
}
|
|
300
427
|
const app = await startMonoAgentApp({
|
|
301
428
|
cwd: process.cwd(),
|
|
302
429
|
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
303
|
-
...(args.port === undefined ? {} : { operatorConsolePort: args.port }),
|
|
304
|
-
...(args.noConsole ? { operatorConsole: false } : {}),
|
|
305
430
|
logger: consoleLogger(),
|
|
306
431
|
});
|
|
307
432
|
printAppStatus(app);
|
|
308
|
-
|
|
309
|
-
|
|
433
|
+
// Block until a shutdown signal. Returning here (the old behavior) let the
|
|
434
|
+
// process exit immediately whenever no channel owned a live handle — e.g. a
|
|
435
|
+
// traceability-only config, now that the operator console is retired and the
|
|
436
|
+
// trace heartbeat timer is unref'd.
|
|
437
|
+
return await waitForShutdownSignal(app);
|
|
310
438
|
}
|
|
311
439
|
async function runBackgroundCommand(args, command) {
|
|
312
440
|
const guard = requireDarwin(command);
|
|
313
441
|
if (guard !== undefined) {
|
|
314
442
|
return guard;
|
|
315
443
|
}
|
|
444
|
+
// Refuse to launch (or relaunch) an unconfigured/broken folder BEFORE writing
|
|
445
|
+
// the plist and bootstrapping launchctl — otherwise the worker would crash and
|
|
446
|
+
// launchd's KeepAlive would retry it forever. stop/status/logs stay ungated so
|
|
447
|
+
// a broken instance can still be inspected and torn down.
|
|
448
|
+
if (command === "start" || command === "restart") {
|
|
449
|
+
const pre = await ensureStartable(args);
|
|
450
|
+
if (!pre.ok) {
|
|
451
|
+
printPreflightFailure(pre);
|
|
452
|
+
return pre.code;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
316
455
|
const target = await resolveInstanceTarget({
|
|
317
456
|
args: {
|
|
318
457
|
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
319
458
|
...(args.envFile === undefined ? {} : { envFile: args.envFile }),
|
|
320
|
-
...(args.port === undefined ? {} : { port: args.port }),
|
|
321
|
-
noConsole: args.noConsole,
|
|
322
459
|
},
|
|
323
460
|
env: process.env,
|
|
324
461
|
cwd: process.cwd(),
|
|
@@ -329,7 +466,7 @@ async function runBackgroundCommand(args, command) {
|
|
|
329
466
|
case "start":
|
|
330
467
|
return await startBackground(target, deps);
|
|
331
468
|
case "restart":
|
|
332
|
-
return await restartBackground(target, deps);
|
|
469
|
+
return args.force ? await runForceRestart(target, deps) : await restartBackground(target, deps);
|
|
333
470
|
case "stop":
|
|
334
471
|
return await stopBackground(target, deps);
|
|
335
472
|
case "status":
|
|
@@ -338,6 +475,27 @@ async function runBackgroundCommand(args, command) {
|
|
|
338
475
|
return await tailLogs(target, deps, { follow: args.follow, lines: args.lines ?? DEFAULT_LOG_LINES });
|
|
339
476
|
}
|
|
340
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* `restart --force`: stop the worker, purge its persisted pi-session store, then
|
|
480
|
+
* start fresh. Stopping first guarantees the worker is not writing sessions while
|
|
481
|
+
* they are deleted; the runtime recreates the store on the next session, and the
|
|
482
|
+
* agent's durable memory lives elsewhere, so only resumable transcripts are dropped.
|
|
483
|
+
*/
|
|
484
|
+
async function runForceRestart(target, deps) {
|
|
485
|
+
const stopCode = await stopBackground(target, deps);
|
|
486
|
+
if (stopCode !== 0) {
|
|
487
|
+
return stopCode;
|
|
488
|
+
}
|
|
489
|
+
const result = await purgeSessions({ env: process.env, cwd: target.cwd, configPath: target.configPath });
|
|
490
|
+
if (result.removed) {
|
|
491
|
+
const count = result.files === 0 ? "" : ` (${result.files} session file${result.files === 1 ? "" : "s"})`;
|
|
492
|
+
process.stdout.write(`${ui.badge("ok")}${ui.style.bold("Cleared persisted sessions")}${count}.\n`);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
process.stdout.write(ui.style.dim("No persisted sessions to clear (in-memory or none on disk).") + "\n");
|
|
496
|
+
}
|
|
497
|
+
return await startBackground(target, deps);
|
|
498
|
+
}
|
|
341
499
|
/**
|
|
342
500
|
* Background service mode is launchd-specific. On other platforms point the
|
|
343
501
|
* user at the still-supported blocking foreground path.
|
|
@@ -346,22 +504,53 @@ function requireDarwin(command) {
|
|
|
346
504
|
if (process.platform === "darwin") {
|
|
347
505
|
return undefined;
|
|
348
506
|
}
|
|
349
|
-
process.stderr.write(`Background service mode (mono-agent ${command}) requires macOS (launchd)
|
|
350
|
-
|
|
507
|
+
process.stderr.write(ui.errorLine(`Background service mode (mono-agent ${command}) requires macOS (launchd).`));
|
|
508
|
+
process.stderr.write(ui.hint("Run `mono-agent start --foreground` to run in the foreground on this platform."));
|
|
351
509
|
return 1;
|
|
352
510
|
}
|
|
353
|
-
function printAppStatus(app) {
|
|
354
|
-
if (app.operatorConsole !== undefined) {
|
|
355
|
-
process.stdout.write(`operator console ${app.operatorConsole.appUrl}\n`);
|
|
356
|
-
}
|
|
357
|
-
process.stdout.write(`config ${app.configPath}\n`);
|
|
511
|
+
export function printAppStatus(app) {
|
|
358
512
|
const trace = app.traceabilityStatus;
|
|
359
|
-
process.stdout.write(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
513
|
+
process.stdout.write(ui.rule("instance"));
|
|
514
|
+
process.stdout.write(ui.keyValue([
|
|
515
|
+
["config", app.configPath],
|
|
516
|
+
[
|
|
517
|
+
"traceability",
|
|
518
|
+
trace.kind === "running" ? `running (source ${trace.sourceId})` : `${trace.kind}: ${trace.reason}`,
|
|
519
|
+
],
|
|
520
|
+
], 2));
|
|
521
|
+
const artifactDir = app.traceabilityStatus.kind === "running" ? app.traceabilityStatus.artifactDir : undefined;
|
|
522
|
+
process.stdout.write(ui.rule("observability"));
|
|
523
|
+
process.stdout.write(` ${describeExporter(app.exporterStatus, artifactDir)}\n`);
|
|
524
|
+
const channels = [...app.channelStatuses()];
|
|
525
|
+
if (channels.length > 0) {
|
|
526
|
+
process.stdout.write(ui.rule("channels"));
|
|
527
|
+
for (const [id, status] of channels) {
|
|
528
|
+
process.stdout.write(` ${ui.channelBadge(status.kind)}${ui.style.bold(id.padEnd(11))} ${describeChannelStatus(status)}\n`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function describeExporter(status, artifactDir) {
|
|
533
|
+
if (status.kind !== "configured") {
|
|
534
|
+
return `${status.kind}: ${status.reason}`;
|
|
535
|
+
}
|
|
536
|
+
const parts = [`phoenix ${status.endpoint}`];
|
|
537
|
+
const appUrl = phoenixAppBaseUrl(status.endpoint);
|
|
538
|
+
if (appUrl !== undefined) {
|
|
539
|
+
parts.push(`app ${appUrl}`);
|
|
540
|
+
}
|
|
541
|
+
if (status.includeSensitiveData) {
|
|
542
|
+
parts.push("includeSensitiveData=true");
|
|
364
543
|
}
|
|
544
|
+
if (status.lastWarning !== undefined) {
|
|
545
|
+
parts.push(`last warning: ${status.lastWarning}`);
|
|
546
|
+
}
|
|
547
|
+
if (status.lastError !== undefined) {
|
|
548
|
+
parts.push(`last error: ${status.lastError}`);
|
|
549
|
+
}
|
|
550
|
+
parts.push(artifactDir === undefined
|
|
551
|
+
? "JSONL artifacts remain local"
|
|
552
|
+
: `JSONL artifacts remain local at ${artifactDir}`);
|
|
553
|
+
return parts.join("; ");
|
|
365
554
|
}
|
|
366
555
|
function describeChannelStatus(status) {
|
|
367
556
|
if (status.kind === "running") {
|
|
@@ -372,19 +561,34 @@ function describeChannelStatus(status) {
|
|
|
372
561
|
}
|
|
373
562
|
return `${status.kind}: ${status.reason}`;
|
|
374
563
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
564
|
+
/**
|
|
565
|
+
* Block the foreground process until SIGINT/SIGTERM, then stop the app and
|
|
566
|
+
* resolve the exit code. A referenced no-op timer owns the event loop so the
|
|
567
|
+
* process stays alive even with no channel handle (signal listeners alone do
|
|
568
|
+
* NOT keep Node running, and the trace heartbeat is unref'd). Cleared on stop so
|
|
569
|
+
* the loop drains cleanly without a forceful `process.exit`. Exported for tests.
|
|
570
|
+
*/
|
|
571
|
+
export function waitForShutdownSignal(app) {
|
|
572
|
+
return new Promise((resolve) => {
|
|
573
|
+
const keepAlive = setInterval(() => { }, KEEP_ALIVE_INTERVAL_MS);
|
|
574
|
+
let stopping = false;
|
|
575
|
+
const onSignal = (signal) => {
|
|
576
|
+
if (stopping) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
stopping = true;
|
|
580
|
+
process.off("SIGINT", onSignal);
|
|
581
|
+
process.off("SIGTERM", onSignal);
|
|
582
|
+
clearInterval(keepAlive);
|
|
583
|
+
void (async () => {
|
|
584
|
+
process.stdout.write("\n" + ui.hint(`Received ${signal}; stopping mono agent app…`));
|
|
585
|
+
await app.stop();
|
|
586
|
+
resolve(0);
|
|
587
|
+
})();
|
|
588
|
+
};
|
|
589
|
+
process.on("SIGINT", onSignal);
|
|
590
|
+
process.on("SIGTERM", onSignal);
|
|
591
|
+
});
|
|
388
592
|
}
|
|
389
593
|
function consoleLogger() {
|
|
390
594
|
return {
|
|
@@ -409,7 +613,7 @@ if (isDirectCliInvocation) {
|
|
|
409
613
|
}
|
|
410
614
|
})
|
|
411
615
|
.catch((error) => {
|
|
412
|
-
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
616
|
+
process.stderr.write(`${ui.style.red("✗")} ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
413
617
|
process.exitCode = 1;
|
|
414
618
|
});
|
|
415
619
|
}
|