@mono-agent/agent-app 0.4.0 → 0.5.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 +15 -5
- package/dist/adapter-send-tools-main.js +2 -2
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +72 -6
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +413 -38
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts +48 -11
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +98 -24
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +10 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +659 -48
- package/dist/app.js.map +1 -1
- package/dist/artifact-retention.d.ts +32 -0
- package/dist/artifact-retention.d.ts.map +1 -0
- package/dist/artifact-retention.js +111 -0
- package/dist/artifact-retention.js.map +1 -0
- package/dist/audit-runs.d.ts +12 -0
- package/dist/audit-runs.d.ts.map +1 -0
- package/dist/audit-runs.js +68 -0
- package/dist/audit-runs.js.map +1 -0
- package/dist/backfill.d.ts +3 -7
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +115 -11
- package/dist/backfill.js.map +1 -1
- package/dist/background.d.ts +8 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +202 -4
- package/dist/background.js.map +1 -1
- package/dist/broadcast-recorder.d.ts +27 -0
- package/dist/broadcast-recorder.d.ts.map +1 -0
- package/dist/broadcast-recorder.js +82 -0
- package/dist/broadcast-recorder.js.map +1 -0
- package/dist/channel-config-view.d.ts +28 -0
- package/dist/channel-config-view.d.ts.map +1 -0
- package/dist/channel-config-view.js +79 -0
- package/dist/channel-config-view.js.map +1 -0
- package/dist/channel-gate.d.ts +22 -0
- package/dist/channel-gate.d.ts.map +1 -0
- package/dist/channel-gate.js +35 -0
- package/dist/channel-gate.js.map +1 -0
- package/dist/channel-plugins.d.ts +39 -0
- package/dist/channel-plugins.d.ts.map +1 -0
- package/dist/channel-plugins.js +372 -0
- package/dist/channel-plugins.js.map +1 -0
- package/dist/channels.d.ts +62 -73
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +833 -126
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +65 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +755 -30
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts +24 -0
- package/dist/config-reference.d.ts.map +1 -0
- package/dist/config-reference.js +580 -0
- package/dist/config-reference.js.map +1 -0
- package/dist/configured-agent.d.ts +104 -0
- package/dist/configured-agent.d.ts.map +1 -0
- package/dist/configured-agent.js +578 -0
- package/dist/configured-agent.js.map +1 -0
- package/dist/consumer-contract.d.ts +36 -0
- package/dist/consumer-contract.d.ts.map +1 -0
- package/dist/consumer-contract.js +290 -0
- package/dist/consumer-contract.js.map +1 -0
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +352 -55
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +23 -7
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +91 -26
- package/dist/init.js.map +1 -1
- package/dist/install-skill.d.ts +1 -1
- package/dist/install-skill.js +2 -2
- package/dist/install-skill.js.map +1 -1
- package/dist/interaction-bridge.d.ts +72 -0
- package/dist/interaction-bridge.d.ts.map +1 -0
- package/dist/interaction-bridge.js +354 -0
- package/dist/interaction-bridge.js.map +1 -0
- package/dist/memory-command.d.ts +10 -0
- package/dist/memory-command.d.ts.map +1 -0
- package/dist/memory-command.js +649 -0
- package/dist/memory-command.js.map +1 -0
- package/dist/memory-recall-main.js +1 -1
- package/dist/memory-recall-main.js.map +1 -1
- package/dist/memory-recall.d.ts +41 -5
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +75 -5
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-rituals.d.ts +8 -11
- package/dist/memory-rituals.d.ts.map +1 -1
- package/dist/memory-rituals.js +26 -25
- package/dist/memory-rituals.js.map +1 -1
- package/dist/metrics.d.ts +13 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +105 -0
- package/dist/metrics.js.map +1 -0
- package/dist/notify-destinations.d.ts +23 -0
- package/dist/notify-destinations.d.ts.map +1 -0
- package/dist/notify-destinations.js +79 -0
- package/dist/notify-destinations.js.map +1 -0
- package/dist/posted-message-index.d.ts +61 -0
- package/dist/posted-message-index.d.ts.map +1 -0
- package/dist/posted-message-index.js +178 -0
- package/dist/posted-message-index.js.map +1 -0
- package/dist/proactive-notify.d.ts +32 -0
- package/dist/proactive-notify.d.ts.map +1 -0
- package/dist/proactive-notify.js +62 -0
- package/dist/proactive-notify.js.map +1 -0
- package/dist/recipes/base.d.ts +15 -0
- package/dist/recipes/base.d.ts.map +1 -0
- package/dist/recipes/base.js +51 -0
- package/dist/recipes/base.js.map +1 -0
- package/dist/recipes/catalog.d.ts +4 -0
- package/dist/recipes/catalog.d.ts.map +1 -0
- package/dist/recipes/catalog.js +525 -0
- package/dist/recipes/catalog.js.map +1 -0
- package/dist/recipes/index.d.ts +11 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +14 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/types.d.ts +70 -0
- package/dist/recipes/types.d.ts.map +1 -0
- package/dist/recipes/types.js +15 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/request-model-override.d.ts +67 -0
- package/dist/request-model-override.d.ts.map +1 -0
- package/dist/request-model-override.js +96 -0
- package/dist/request-model-override.js.map +1 -0
- package/dist/runs-health.d.ts +22 -0
- package/dist/runs-health.d.ts.map +1 -0
- package/dist/runs-health.js +144 -0
- package/dist/runs-health.js.map +1 -0
- package/dist/seen-conversations.d.ts +22 -0
- package/dist/seen-conversations.d.ts.map +1 -0
- package/dist/seen-conversations.js +120 -0
- package/dist/seen-conversations.js.map +1 -0
- package/dist/setup.d.ts +29 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +97 -0
- package/dist/setup.js.map +1 -0
- package/dist/trigger-overrides.d.ts +17 -0
- package/dist/trigger-overrides.d.ts.map +1 -0
- package/dist/trigger-overrides.js +30 -0
- package/dist/trigger-overrides.js.map +1 -0
- package/dist/tui-command.d.ts +56 -0
- package/dist/tui-command.d.ts.map +1 -0
- package/dist/tui-command.js +160 -0
- package/dist/tui-command.js.map +1 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +4 -0
- package/dist/ui.js.map +1 -1
- package/dist/web-command.d.ts +45 -0
- package/dist/web-command.d.ts.map +1 -0
- package/dist/web-command.js +152 -0
- package/dist/web-command.js.map +1 -0
- package/package.json +19 -18
- package/schema/mono-agent.config.schema.json +1686 -0
- package/skills/mono-agent-composer/SKILL.md +34 -9
- package/skills/mono-agent-composer/agents/openai.yaml +4 -0
- package/skills/mono-agent-composer/references/config-blueprint.md +92 -49
- package/skills/mono-agent-composer/references/discovery-questions.md +15 -17
- package/skills/mono-agent-composer/references/feature-coverage.md +22 -17
- package/skills/mono-agent-composer/references/package-map.md +19 -21
- package/skills/mono-agent-composer/references/playbooks.md +267 -0
- package/skills/mono-agent-composer/references/validation.md +9 -1
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
2
3
|
import { stat } from "node:fs/promises";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
3
5
|
import { basename, resolve } from "node:path";
|
|
4
6
|
import process from "node:process";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { buildMonoAgentConfigView, findJsonSecretConfigWarnings, findRemovedConfigWarnings, readMonoAgentConfigJson, redactMonoAgentConfig, } from "@mono-agent/config";
|
|
9
|
+
import { listRecordedRuns } from "@mono-agent/observability";
|
|
10
|
+
import { describeSandboxEffectiveState, sandboxEffectiveStateWarning, } from "@mono-agent/runtime-adapter";
|
|
6
11
|
import { startMonoAgentApp } from "./app.js";
|
|
7
|
-
import { phoenixAppBaseUrl } from "./app-config.js";
|
|
12
|
+
import { describeSensitiveDataExportWarning, isAppCoreConfigError, loadAppCoreConfig, phoenixAppBaseUrl, } from "./app-config.js";
|
|
13
|
+
import { runAuditRuns } from "./audit-runs.js";
|
|
8
14
|
import { runBackfill } from "./backfill.js";
|
|
9
15
|
import { defaultBackgroundDeps, resolveInstanceTarget, restartBackground, startBackground, statusBackground, stopBackground, tailLogs, } from "./background.js";
|
|
16
|
+
import { collectChannelConfigViews } from "./channel-config-view.js";
|
|
17
|
+
import { resolveChannelDrivers } from "./channels.js";
|
|
18
|
+
import { findUnknownAppConfigWarnings } from "./config-reference.js";
|
|
10
19
|
import { validateMonoAgentFolder } from "./doctor.js";
|
|
11
|
-
import { initMonoAgentFolder } from "./init.js";
|
|
20
|
+
import { initMonoAgentFolder, isWithChannel } from "./init.js";
|
|
12
21
|
import { installComposerSkill } from "./install-skill.js";
|
|
22
|
+
import { runMetrics } from "./metrics.js";
|
|
23
|
+
import { findRecipe, RECIPE_CATALOG, recipeIds, resolveRecipeInputs } from "./recipes/index.js";
|
|
24
|
+
import { buildRunsHealthDisplay, RUNS_HEALTH_MAX_RUNS } from "./runs-health.js";
|
|
13
25
|
import { purgeSessions } from "./sessions.js";
|
|
26
|
+
import { collectSetupOptions } from "./setup.js";
|
|
14
27
|
import * as ui from "./ui.js";
|
|
15
28
|
const DEFAULT_LOG_LINES = 200;
|
|
16
29
|
// Node's maximum setInterval/setTimeout delay (2^31 - 1 ms, ~24.8 days). A
|
|
@@ -18,21 +31,29 @@ const DEFAULT_LOG_LINES = 200;
|
|
|
18
31
|
// busy-waiting; larger values silently overflow to a 1ms delay.
|
|
19
32
|
const KEEP_ALIVE_INTERVAL_MS = 2_147_483_647;
|
|
20
33
|
const BACKGROUND_COMMANDS = ["start", "restart", "stop", "status", "logs"];
|
|
21
|
-
const KNOWN_COMMANDS = ["init", "validate", "start", "restart", "stop", "status", "logs", "install-skill", "backfill"];
|
|
34
|
+
const KNOWN_COMMANDS = ["init", "setup", "validate", "doctor", "config", "recipes", "start", "restart", "stop", "status", "logs", "tui", "web", "install-skill", "backfill", "audit-runs", "metrics", "memory"];
|
|
22
35
|
export function parseCliArgs(argv) {
|
|
23
36
|
const [command, ...rest] = argv;
|
|
24
37
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
25
|
-
return { command: "help", force: false, foreground: false, follow: false, all: false, dryRun: false };
|
|
38
|
+
return { command: "help", positionals: [], force: false, foreground: false, follow: false, all: false, dryRun: false, includeMemory: false };
|
|
39
|
+
}
|
|
40
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
41
|
+
return { command: "version", positionals: [], force: false, foreground: false, follow: false, all: false, dryRun: false, includeMemory: false };
|
|
26
42
|
}
|
|
27
43
|
if (!KNOWN_COMMANDS.includes(command)) {
|
|
28
44
|
throw new Error(`Unknown command \`${command}\`. Expected ${KNOWN_COMMANDS.join(", ")}.`);
|
|
29
45
|
}
|
|
30
|
-
|
|
46
|
+
// `doctor` is an alias of `validate`; normalize here so every downstream
|
|
47
|
+
// validate path (env-file resolution, --consumer, routing) applies unchanged.
|
|
48
|
+
const cmd = (command === "doctor" ? "validate" : command);
|
|
31
49
|
const isLogs = cmd === "logs";
|
|
32
50
|
let configPath;
|
|
33
51
|
let model;
|
|
34
52
|
let fallbackModels;
|
|
35
53
|
let memory;
|
|
54
|
+
let recipe;
|
|
55
|
+
let withChannels;
|
|
56
|
+
const positionals = [];
|
|
36
57
|
let envFile;
|
|
37
58
|
let target;
|
|
38
59
|
let force = false;
|
|
@@ -44,6 +65,20 @@ export function parseCliArgs(argv) {
|
|
|
44
65
|
let since;
|
|
45
66
|
let until;
|
|
46
67
|
let dryRun = false;
|
|
68
|
+
let includeMemory = false;
|
|
69
|
+
let artifactDir;
|
|
70
|
+
let groupBy;
|
|
71
|
+
let consumerPath;
|
|
72
|
+
let agent;
|
|
73
|
+
let conversation;
|
|
74
|
+
let staleAfterMs;
|
|
75
|
+
let json = false;
|
|
76
|
+
let limit;
|
|
77
|
+
let host;
|
|
78
|
+
let port;
|
|
79
|
+
let open;
|
|
80
|
+
let allowNonLoopback;
|
|
81
|
+
let maxRunsPerInstance;
|
|
47
82
|
for (let i = 0; i < rest.length; i += 1) {
|
|
48
83
|
const flag = rest[i];
|
|
49
84
|
switch (flag) {
|
|
@@ -65,6 +100,80 @@ export function parseCliArgs(argv) {
|
|
|
65
100
|
case "--dry-run":
|
|
66
101
|
dryRun = true;
|
|
67
102
|
break;
|
|
103
|
+
case "--include-memory":
|
|
104
|
+
includeMemory = true;
|
|
105
|
+
break;
|
|
106
|
+
case "--artifact-dir":
|
|
107
|
+
artifactDir = requireValue(rest, ++i, flag);
|
|
108
|
+
break;
|
|
109
|
+
case "--artifacts":
|
|
110
|
+
artifactDir = requireValue(rest, ++i, flag);
|
|
111
|
+
break;
|
|
112
|
+
case "--by": {
|
|
113
|
+
const raw = requireValue(rest, ++i, flag);
|
|
114
|
+
if (raw !== "model" && raw !== "channel" && raw !== "failureKind") {
|
|
115
|
+
throw new Error("--by must be model, channel, or failureKind.");
|
|
116
|
+
}
|
|
117
|
+
groupBy = raw;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "--consumer":
|
|
121
|
+
consumerPath = requireValue(rest, ++i, flag);
|
|
122
|
+
break;
|
|
123
|
+
case "--agent":
|
|
124
|
+
agent = requireValue(rest, ++i, flag);
|
|
125
|
+
break;
|
|
126
|
+
case "--conversation":
|
|
127
|
+
conversation = requireValue(rest, ++i, flag);
|
|
128
|
+
break;
|
|
129
|
+
case "--stale-after-ms": {
|
|
130
|
+
const raw = requireValue(rest, ++i, flag);
|
|
131
|
+
const parsed = Number(raw);
|
|
132
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
133
|
+
throw new Error("--stale-after-ms must be a positive integer.");
|
|
134
|
+
}
|
|
135
|
+
staleAfterMs = parsed;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "--json":
|
|
139
|
+
json = true;
|
|
140
|
+
break;
|
|
141
|
+
case "--limit": {
|
|
142
|
+
const raw = requireValue(rest, ++i, flag);
|
|
143
|
+
const parsed = Number(raw);
|
|
144
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
|
|
145
|
+
throw new Error("--limit must be an integer between 1 and 100.");
|
|
146
|
+
}
|
|
147
|
+
limit = parsed;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "--host":
|
|
151
|
+
host = requireValue(rest, ++i, flag);
|
|
152
|
+
break;
|
|
153
|
+
case "--port": {
|
|
154
|
+
const raw = requireValue(rest, ++i, flag);
|
|
155
|
+
const parsed = Number(raw);
|
|
156
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
|
|
157
|
+
throw new Error("--port must be an integer between 0 and 65535.");
|
|
158
|
+
}
|
|
159
|
+
port = parsed;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "--no-open":
|
|
163
|
+
open = false;
|
|
164
|
+
break;
|
|
165
|
+
case "--allow-non-loopback":
|
|
166
|
+
allowNonLoopback = true;
|
|
167
|
+
break;
|
|
168
|
+
case "--max-runs": {
|
|
169
|
+
const raw = requireValue(rest, ++i, flag);
|
|
170
|
+
const parsed = Number(raw);
|
|
171
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
172
|
+
throw new Error("--max-runs must be a positive integer.");
|
|
173
|
+
}
|
|
174
|
+
maxRunsPerInstance = parsed;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
68
177
|
case "--model":
|
|
69
178
|
model = requireValue(rest, ++i, flag);
|
|
70
179
|
break;
|
|
@@ -82,6 +191,15 @@ export function parseCliArgs(argv) {
|
|
|
82
191
|
memory = raw;
|
|
83
192
|
break;
|
|
84
193
|
}
|
|
194
|
+
case "--recipe":
|
|
195
|
+
recipe = requireValue(rest, ++i, flag);
|
|
196
|
+
break;
|
|
197
|
+
case "--with":
|
|
198
|
+
withChannels = requireValue(rest, ++i, flag)
|
|
199
|
+
.split(",")
|
|
200
|
+
.map((entry) => entry.trim())
|
|
201
|
+
.filter((entry) => entry.length > 0);
|
|
202
|
+
break;
|
|
85
203
|
case "--env-file":
|
|
86
204
|
envFile = requireValue(rest, ++i, flag);
|
|
87
205
|
break;
|
|
@@ -121,15 +239,39 @@ export function parseCliArgs(argv) {
|
|
|
121
239
|
break;
|
|
122
240
|
}
|
|
123
241
|
default:
|
|
124
|
-
|
|
242
|
+
if (flag === undefined) {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
if (flag.startsWith("--")) {
|
|
246
|
+
throw new Error(`Unknown flag \`${flag}\` for \`mono-agent ${command}\`.`);
|
|
247
|
+
}
|
|
248
|
+
// Non-flag tokens are positional arguments (e.g. `recipes show <id>`).
|
|
249
|
+
positionals.push(flag);
|
|
250
|
+
break;
|
|
125
251
|
}
|
|
126
252
|
}
|
|
253
|
+
if (consumerPath !== undefined && cmd !== "validate" && cmd !== "audit-runs") {
|
|
254
|
+
throw new Error("--consumer is only supported for `mono-agent validate` and `mono-agent audit-runs`.");
|
|
255
|
+
}
|
|
256
|
+
if ((host !== undefined || port !== undefined || open !== undefined || allowNonLoopback !== undefined || maxRunsPerInstance !== undefined) &&
|
|
257
|
+
cmd !== "web") {
|
|
258
|
+
throw new Error("--host, --port, --no-open, --allow-non-loopback, and --max-runs are only supported for `mono-agent web`.");
|
|
259
|
+
}
|
|
260
|
+
if (includeMemory && cmd !== "audit-runs" && cmd !== "metrics" && cmd !== "backfill" && cmd !== "web") {
|
|
261
|
+
throw new Error("--include-memory is only supported for `mono-agent audit-runs`, `mono-agent metrics`, `mono-agent backfill`, and `mono-agent web`.");
|
|
262
|
+
}
|
|
263
|
+
if (limit !== undefined && cmd !== "memory") {
|
|
264
|
+
throw new Error("--limit is only supported for `mono-agent memory`.");
|
|
265
|
+
}
|
|
127
266
|
return {
|
|
128
267
|
command: cmd,
|
|
129
268
|
...(configPath === undefined ? {} : { configPath }),
|
|
130
269
|
...(model === undefined ? {} : { model }),
|
|
131
270
|
...(fallbackModels === undefined ? {} : { fallbackModels }),
|
|
132
271
|
...(memory === undefined ? {} : { memory }),
|
|
272
|
+
...(recipe === undefined ? {} : { recipe }),
|
|
273
|
+
...(withChannels === undefined ? {} : { withChannels }),
|
|
274
|
+
positionals,
|
|
133
275
|
...(envFile === undefined ? {} : { envFile }),
|
|
134
276
|
...(target === undefined ? {} : { target }),
|
|
135
277
|
force,
|
|
@@ -141,6 +283,20 @@ export function parseCliArgs(argv) {
|
|
|
141
283
|
...(since === undefined ? {} : { since }),
|
|
142
284
|
...(until === undefined ? {} : { until }),
|
|
143
285
|
dryRun,
|
|
286
|
+
includeMemory,
|
|
287
|
+
...(artifactDir === undefined ? {} : { artifactDir }),
|
|
288
|
+
...(groupBy === undefined ? {} : { groupBy }),
|
|
289
|
+
...(consumerPath === undefined ? {} : { consumerPath }),
|
|
290
|
+
...(staleAfterMs === undefined ? {} : { staleAfterMs }),
|
|
291
|
+
...(json ? { json } : {}),
|
|
292
|
+
...(limit === undefined ? {} : { limit }),
|
|
293
|
+
...(agent === undefined ? {} : { agent }),
|
|
294
|
+
...(conversation === undefined ? {} : { conversation }),
|
|
295
|
+
...(host === undefined ? {} : { host }),
|
|
296
|
+
...(port === undefined ? {} : { port }),
|
|
297
|
+
...(open === undefined ? {} : { open }),
|
|
298
|
+
...(allowNonLoopback === undefined ? {} : { allowNonLoopback }),
|
|
299
|
+
...(maxRunsPerInstance === undefined ? {} : { maxRunsPerInstance }),
|
|
144
300
|
};
|
|
145
301
|
}
|
|
146
302
|
/**
|
|
@@ -157,6 +313,15 @@ export function loadCliEnvFile(path) {
|
|
|
157
313
|
return false;
|
|
158
314
|
}
|
|
159
315
|
}
|
|
316
|
+
function resolveValidateContext(args, invocationCwd) {
|
|
317
|
+
const cwd = resolve(invocationCwd, args.consumerPath ?? ".");
|
|
318
|
+
return {
|
|
319
|
+
cwd,
|
|
320
|
+
configPath: resolve(cwd, args.configPath ?? "mono-agent.config.json"),
|
|
321
|
+
envFilePath: resolve(cwd, args.envFile ?? ".env"),
|
|
322
|
+
allowFilesystemWrites: args.consumerPath === undefined,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
160
325
|
function requireValue(args, index, flag) {
|
|
161
326
|
const value = args[index];
|
|
162
327
|
if (value === undefined || value.startsWith("--")) {
|
|
@@ -166,15 +331,45 @@ function requireValue(args, index, flag) {
|
|
|
166
331
|
}
|
|
167
332
|
const HELP_COMMANDS = [
|
|
168
333
|
{
|
|
169
|
-
signature: "mono-agent init [--
|
|
334
|
+
signature: "mono-agent init [--recipe <id>] [--with <csv>] [--dry-run]\n" +
|
|
335
|
+
" [--model <ref>] [--fallback-models <csv>] [--memory lite|journal|bujo]",
|
|
170
336
|
lines: [
|
|
171
337
|
"Scaffold mono-agent.config.json, IDENTITY.md, and .mono-agent/ in the",
|
|
172
|
-
"current folder.
|
|
338
|
+
"current folder. With --recipe, build from a blueprint (+ .env.example);",
|
|
339
|
+
"--with adds channels, --dry-run previews. Existing files are never overwritten.",
|
|
173
340
|
],
|
|
174
341
|
},
|
|
175
342
|
{
|
|
176
|
-
signature: "mono-agent
|
|
177
|
-
|
|
343
|
+
signature: "mono-agent setup [--recipe <id>] [--with <csv>] [--dry-run]\n" +
|
|
344
|
+
" [--model <ref>] [--fallback-models <csv>] [--memory lite|journal|bujo]",
|
|
345
|
+
lines: [
|
|
346
|
+
"Guided setup when attached to a TTY: choose a recipe, answer non-secret",
|
|
347
|
+
"inputs, select channel add-ons, scaffold, validate, and print the secrets",
|
|
348
|
+
"checklist. In non-TTY contexts it falls back to init-style flags.",
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
signature: "mono-agent recipes list | show <id>",
|
|
353
|
+
lines: [
|
|
354
|
+
"List the executable config blueprints, or show one's generated config,",
|
|
355
|
+
".env.example, and follow-up checklist.",
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
signature: "mono-agent validate [--recipe <id>] [--consumer <path>] [--config <path>] [--env-file <path>]",
|
|
360
|
+
lines: [
|
|
361
|
+
"Load every config section and report what would run, wait, or fail.",
|
|
362
|
+
"--consumer validates another agent folder read-only, including its .env.",
|
|
363
|
+
"With --recipe, also report whether the recipe's capabilities are live.",
|
|
364
|
+
"`mono-agent doctor` is an alias for this command.",
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
signature: "mono-agent config [--config <path>] [--env-file <path>]",
|
|
369
|
+
lines: [
|
|
370
|
+
"Print the resolved config field-by-field, tagging each value with where",
|
|
371
|
+
"it came from (env / json / default), plus the channel summary. Read-only.",
|
|
372
|
+
],
|
|
178
373
|
},
|
|
179
374
|
{
|
|
180
375
|
signature: "mono-agent start [--config <path>] [--env-file <path>] [--foreground|-f]",
|
|
@@ -205,21 +400,70 @@ const HELP_COMMANDS = [
|
|
|
205
400
|
signature: "mono-agent logs [--config <path>] [--follow|-f] [--lines <n>]",
|
|
206
401
|
lines: ["Print (and optionally follow) the background instance's log files."],
|
|
207
402
|
},
|
|
403
|
+
{
|
|
404
|
+
signature: "mono-agent tui [--agent <label|sourceId>] [--conversation <id>]",
|
|
405
|
+
lines: [
|
|
406
|
+
"Open the operator console from any directory: live chat with full",
|
|
407
|
+
"thinking/tool/telemetry insight, recorded-run replay, and config view.",
|
|
408
|
+
"Discovers running agents via the trace-source registry; one running",
|
|
409
|
+
"agent connects directly, several open a picker.",
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
signature: "mono-agent web [--host <addr>] [--port <n>] [--no-open] [--allow-non-loopback] [--include-memory] [--max-runs <n>]",
|
|
414
|
+
lines: [
|
|
415
|
+
"Serve the read-only Session Recorder web PWA from any directory: a live",
|
|
416
|
+
"flight-recorder over every agent's runs (prompt, reasoning, tools, cost).",
|
|
417
|
+
"Discovers running agents via the trace-source registry — the same",
|
|
418
|
+
"mechanism as `tui` — and streams new/updated runs in real time.",
|
|
419
|
+
"--include-memory also shows memory-maintenance runs. --max-runs (default",
|
|
420
|
+
"200) bounds the in-memory working set; the UI still pages the full",
|
|
421
|
+
"on-disk history via \"Load older\".",
|
|
422
|
+
],
|
|
423
|
+
},
|
|
208
424
|
{
|
|
209
425
|
signature: "mono-agent install-skill [--target claude|codex|both] [--force]",
|
|
210
426
|
lines: [
|
|
211
427
|
"Copy the bundled mono-agent-composer skill into ~/.claude/skills and",
|
|
212
|
-
"~/.
|
|
428
|
+
"~/.agents/skills (default: both). Refuses to overwrite without --force.",
|
|
213
429
|
],
|
|
214
430
|
},
|
|
215
431
|
{
|
|
216
432
|
signature: "mono-agent backfill (--run <id> | --all) [--since <iso>] [--until <iso>]\n" +
|
|
217
|
-
" [--dry-run] [--config <path>] [--env-file <path>]",
|
|
433
|
+
" [--include-memory] [--dry-run] [--config <path>] [--env-file <path>]",
|
|
218
434
|
lines: [
|
|
219
|
-
"Export already-recorded run artifacts to the configured Phoenix exporter",
|
|
435
|
+
"Export already-recorded agent-run artifacts to the configured Phoenix exporter",
|
|
220
436
|
"with their historical timestamps. Trace ids are deterministic per run, so",
|
|
221
437
|
"re-running overwrites rather than duplicating. --dry-run maps and",
|
|
222
|
-
"serializes without sending.",
|
|
438
|
+
"serializes without sending. --include-memory adds memory-run artifacts",
|
|
439
|
+
"for --all; explicit --run can target a memory run directly.",
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
signature: "mono-agent audit-runs [--artifact-dir <path> | --consumer <path>]\n" +
|
|
444
|
+
" [--include-memory] [--config <path>] [--env-file <path>] [--stale-after-ms <n>] [--json]",
|
|
445
|
+
lines: [
|
|
446
|
+
"Read local agent-run summary artifacts without rewriting them. Reports parse",
|
|
447
|
+
"failures, status and failure-kind histograms, stale running summaries,",
|
|
448
|
+
"and per-failure-kind rates. --include-memory includes memory-run artifacts.",
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
signature: "mono-agent metrics [--artifacts <path>] [--since <iso>] [--until <iso>]\n" +
|
|
453
|
+
" [--include-memory] [--by model|channel|failureKind] [--json] [--config <path>] [--env-file <path>]",
|
|
454
|
+
lines: [
|
|
455
|
+
"Aggregate local agent-run summaries into status rates, failure-kind rates,",
|
|
456
|
+
"duration percentiles, and total/per-run cost. Read-only and offline.",
|
|
457
|
+
"--include-memory includes memory-run artifacts.",
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
signature: "mono-agent memory [stats|today|show <date>|search <query>|top]\n" +
|
|
462
|
+
" [--limit <n>] [--json] [--config <path>] [--env-file <path>]",
|
|
463
|
+
lines: [
|
|
464
|
+
"Preview the configured memory store from an agent folder. Reads the",
|
|
465
|
+
"memory block from mono-agent.config.json, not the standalone memory-bujo",
|
|
466
|
+
"env workflow. Human-first output by default; --json is for scripts.",
|
|
223
467
|
],
|
|
224
468
|
},
|
|
225
469
|
];
|
|
@@ -252,6 +496,22 @@ export function renderHelp() {
|
|
|
252
496
|
out += ui.style.dim(HELP_NOTES);
|
|
253
497
|
return out;
|
|
254
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* The published CLI version, read from this package's own package.json at
|
|
501
|
+
* runtime. `../package.json` resolves the same from both `src/cli.ts` (tests) and
|
|
502
|
+
* the built `dist/cli.js` (one level below the package root in both layouts), and
|
|
503
|
+
* npm always ships package.json in the tarball. Best-effort: a read failure yields
|
|
504
|
+
* "unknown" rather than crashing `--version`.
|
|
505
|
+
*/
|
|
506
|
+
export function monoAgentVersion() {
|
|
507
|
+
try {
|
|
508
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
509
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return "unknown";
|
|
513
|
+
}
|
|
514
|
+
}
|
|
255
515
|
export async function runCli(argv) {
|
|
256
516
|
let args;
|
|
257
517
|
try {
|
|
@@ -262,15 +522,27 @@ export async function runCli(argv) {
|
|
|
262
522
|
process.stdout.write(`\n${renderHelp()}`);
|
|
263
523
|
return 2;
|
|
264
524
|
}
|
|
265
|
-
|
|
525
|
+
const invocationCwd = process.cwd();
|
|
526
|
+
loadCliEnvFile(args.command === "validate"
|
|
527
|
+
? resolveValidateContext(args, invocationCwd).envFilePath
|
|
528
|
+
: resolve(invocationCwd, args.envFile ?? ".env"));
|
|
266
529
|
switch (args.command) {
|
|
267
530
|
case "help":
|
|
268
531
|
process.stdout.write(renderHelp());
|
|
269
532
|
return 0;
|
|
533
|
+
case "version":
|
|
534
|
+
process.stdout.write(`mono-agent ${monoAgentVersion()}\n`);
|
|
535
|
+
return 0;
|
|
270
536
|
case "init":
|
|
271
537
|
return await runInit(args);
|
|
538
|
+
case "setup":
|
|
539
|
+
return await runSetup(args);
|
|
272
540
|
case "validate":
|
|
273
541
|
return await runValidate(args);
|
|
542
|
+
case "config":
|
|
543
|
+
return await runConfig(args);
|
|
544
|
+
case "recipes":
|
|
545
|
+
return runRecipes(args);
|
|
274
546
|
case "start":
|
|
275
547
|
return await runStart(args);
|
|
276
548
|
case "restart":
|
|
@@ -278,6 +550,32 @@ export async function runCli(argv) {
|
|
|
278
550
|
case "status":
|
|
279
551
|
case "logs":
|
|
280
552
|
return await runBackgroundCommand(args, args.command);
|
|
553
|
+
case "tui": {
|
|
554
|
+
// Lazy import: the operator console (and pi-tui) load only on demand.
|
|
555
|
+
const { runTui } = await import("./tui-command.js");
|
|
556
|
+
return await runTui({
|
|
557
|
+
configPath: resolve(process.cwd(), args.configPath ?? "mono-agent.config.json"),
|
|
558
|
+
cwd: process.cwd(),
|
|
559
|
+
env: process.env,
|
|
560
|
+
...(args.agent === undefined ? {} : { agent: args.agent }),
|
|
561
|
+
...(args.conversation === undefined ? {} : { conversationId: args.conversation }),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
case "web": {
|
|
565
|
+
// Lazy import: the web server (and express/session-web) load only on demand.
|
|
566
|
+
const { runWeb } = await import("./web-command.js");
|
|
567
|
+
return await runWeb({
|
|
568
|
+
configPath: resolve(process.cwd(), args.configPath ?? "mono-agent.config.json"),
|
|
569
|
+
cwd: process.cwd(),
|
|
570
|
+
env: process.env,
|
|
571
|
+
...(args.host === undefined ? {} : { host: args.host }),
|
|
572
|
+
...(args.port === undefined ? {} : { port: args.port }),
|
|
573
|
+
...(args.open === undefined ? {} : { open: args.open }),
|
|
574
|
+
...(args.allowNonLoopback === undefined ? {} : { allowNonLoopback: args.allowNonLoopback }),
|
|
575
|
+
includeMemory: args.includeMemory,
|
|
576
|
+
...(args.maxRunsPerInstance === undefined ? {} : { maxRunsPerInstance: args.maxRunsPerInstance }),
|
|
577
|
+
});
|
|
578
|
+
}
|
|
281
579
|
case "install-skill":
|
|
282
580
|
return await runInstallSkill(args);
|
|
283
581
|
case "backfill":
|
|
@@ -288,31 +586,182 @@ export async function runCli(argv) {
|
|
|
288
586
|
...(args.since === undefined ? {} : { since: args.since }),
|
|
289
587
|
...(args.until === undefined ? {} : { until: args.until }),
|
|
290
588
|
dryRun: args.dryRun,
|
|
589
|
+
includeMemory: args.includeMemory,
|
|
590
|
+
});
|
|
591
|
+
case "audit-runs":
|
|
592
|
+
return await runAuditRuns({
|
|
593
|
+
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
594
|
+
...(args.artifactDir === undefined ? {} : { artifactDir: args.artifactDir }),
|
|
595
|
+
...(args.consumerPath === undefined ? {} : { consumerPath: args.consumerPath }),
|
|
596
|
+
...(args.staleAfterMs === undefined ? {} : { staleAfterMs: args.staleAfterMs }),
|
|
597
|
+
json: args.json === true,
|
|
598
|
+
includeMemory: args.includeMemory,
|
|
599
|
+
});
|
|
600
|
+
case "metrics":
|
|
601
|
+
return await runMetrics({
|
|
602
|
+
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
603
|
+
...(args.artifactDir === undefined ? {} : { artifactDir: args.artifactDir }),
|
|
604
|
+
...(args.since === undefined ? {} : { since: args.since }),
|
|
605
|
+
...(args.until === undefined ? {} : { until: args.until }),
|
|
606
|
+
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
607
|
+
json: args.json === true,
|
|
608
|
+
includeMemory: args.includeMemory,
|
|
609
|
+
});
|
|
610
|
+
case "memory": {
|
|
611
|
+
// Lazy import: the memory preview path pulls SQLite/backend clients only on demand.
|
|
612
|
+
const { runMemoryCommand } = await import("./memory-command.js");
|
|
613
|
+
return await runMemoryCommand({
|
|
614
|
+
cwd: process.cwd(),
|
|
615
|
+
env: process.env,
|
|
616
|
+
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
617
|
+
positionals: args.positionals,
|
|
618
|
+
json: args.json === true,
|
|
619
|
+
...(args.limit === undefined ? {} : { limit: args.limit }),
|
|
291
620
|
});
|
|
621
|
+
}
|
|
292
622
|
}
|
|
293
623
|
}
|
|
294
624
|
async function runInit(args) {
|
|
625
|
+
const recipe = resolveRecipeArg(args);
|
|
626
|
+
if (recipe === "unknown") {
|
|
627
|
+
return 1;
|
|
628
|
+
}
|
|
629
|
+
const withChannels = resolveWithChannels(args);
|
|
630
|
+
if (withChannels === "invalid") {
|
|
631
|
+
return 1;
|
|
632
|
+
}
|
|
295
633
|
const result = await initMonoAgentFolder({
|
|
296
634
|
dir: process.cwd(),
|
|
297
635
|
...(args.model === undefined ? {} : { model: args.model }),
|
|
298
636
|
...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
|
|
299
637
|
...(args.memory === undefined ? {} : { memory: args.memory }),
|
|
638
|
+
...(recipe === undefined ? {} : { recipe }),
|
|
639
|
+
...(withChannels === undefined ? {} : { withChannels }),
|
|
640
|
+
dryRun: args.dryRun,
|
|
641
|
+
});
|
|
642
|
+
printInitResult(result, recipe);
|
|
643
|
+
printNextSteps(result.configPath, recipe);
|
|
644
|
+
return 0;
|
|
645
|
+
}
|
|
646
|
+
async function runSetup(args) {
|
|
647
|
+
if (process.stdin.isTTY !== true) {
|
|
648
|
+
return await runInit(args);
|
|
649
|
+
}
|
|
650
|
+
const recipeArg = resolveRecipeArg(args);
|
|
651
|
+
if (recipeArg === "unknown") {
|
|
652
|
+
return 1;
|
|
653
|
+
}
|
|
654
|
+
const withChannelsArg = resolveWithChannels(args);
|
|
655
|
+
if (withChannelsArg === "invalid") {
|
|
656
|
+
return 1;
|
|
657
|
+
}
|
|
658
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
659
|
+
let collected;
|
|
660
|
+
try {
|
|
661
|
+
collected = await collectSetupOptions({
|
|
662
|
+
prompt: { question: (prompt) => rl.question(prompt) },
|
|
663
|
+
...(recipeArg === undefined ? {} : { recipe: recipeArg }),
|
|
664
|
+
...(args.model === undefined ? {} : { model: args.model }),
|
|
665
|
+
...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
|
|
666
|
+
...(withChannelsArg === undefined ? {} : { withChannels: withChannelsArg }),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
rl.close();
|
|
671
|
+
}
|
|
672
|
+
const result = await initMonoAgentFolder({
|
|
673
|
+
dir: process.cwd(),
|
|
674
|
+
...(collected.fallbackModels.length === 0 ? {} : { fallbackModels: collected.fallbackModels }),
|
|
675
|
+
...(args.memory === undefined ? {} : { memory: args.memory }),
|
|
676
|
+
recipe: collected.recipe,
|
|
677
|
+
recipeInputs: collected.recipeInputs,
|
|
678
|
+
withChannels: collected.withChannels,
|
|
679
|
+
dryRun: args.dryRun,
|
|
300
680
|
});
|
|
681
|
+
printInitResult(result, collected.recipe);
|
|
682
|
+
if (!result.dryRun) {
|
|
683
|
+
const report = await validateMonoAgentFolder({
|
|
684
|
+
env: process.env,
|
|
685
|
+
cwd: process.cwd(),
|
|
686
|
+
configPath: result.configPath,
|
|
687
|
+
});
|
|
688
|
+
process.stdout.write("\n" + ui.heading("Validation"));
|
|
689
|
+
for (const section of report.sections) {
|
|
690
|
+
process.stdout.write(formatSection(section));
|
|
691
|
+
}
|
|
692
|
+
process.stdout.write(renderRecipeCompleteness(collected.recipe, report));
|
|
693
|
+
printSecretsChecklist(collected.secrets);
|
|
694
|
+
printNextSteps(result.configPath, collected.recipe);
|
|
695
|
+
return report.ok ? 0 : 1;
|
|
696
|
+
}
|
|
697
|
+
printSecretsChecklist(collected.secrets);
|
|
698
|
+
printNextSteps(result.configPath, collected.recipe);
|
|
699
|
+
return 0;
|
|
700
|
+
}
|
|
701
|
+
function resolveRecipeArg(args) {
|
|
702
|
+
if (args.recipe === undefined) {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
const recipe = findRecipe(args.recipe);
|
|
706
|
+
if (recipe === undefined) {
|
|
707
|
+
process.stderr.write(ui.errorLine(`Unknown recipe \`${args.recipe}\`.`));
|
|
708
|
+
process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
|
|
709
|
+
return "unknown";
|
|
710
|
+
}
|
|
711
|
+
return recipe;
|
|
712
|
+
}
|
|
713
|
+
function resolveWithChannels(args) {
|
|
714
|
+
if (args.withChannels === undefined) {
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
const invalid = args.withChannels.filter((channel) => !isWithChannel(channel));
|
|
718
|
+
if (invalid.length > 0) {
|
|
719
|
+
process.stderr.write(ui.errorLine(`Unknown --with channel(s): ${invalid.join(", ")}.`));
|
|
720
|
+
process.stderr.write(ui.hint("Valid channels: telegram, slack, webhook, openaiApi, cron."));
|
|
721
|
+
return "invalid";
|
|
722
|
+
}
|
|
723
|
+
return args.withChannels.filter(isWithChannel);
|
|
724
|
+
}
|
|
725
|
+
function printInitResult(result, recipe) {
|
|
726
|
+
if (result.dryRun) {
|
|
727
|
+
process.stdout.write(ui.style.dim("Dry run — nothing was written.\n"));
|
|
728
|
+
}
|
|
729
|
+
const verb = result.dryRun ? "would create" : "created";
|
|
301
730
|
for (const path of result.created) {
|
|
302
|
-
process.stdout.write(`${ui.badge("ok")}${ui.style.green(
|
|
731
|
+
process.stdout.write(`${ui.badge("ok")}${ui.style.green(verb.padEnd(12))} ${path}\n`);
|
|
303
732
|
}
|
|
304
733
|
for (const path of result.skipped) {
|
|
305
|
-
process.stdout.write(ui.style.dim(` kept
|
|
734
|
+
process.stdout.write(ui.style.dim(` kept ${path}`) + "\n");
|
|
306
735
|
}
|
|
307
736
|
if (result.knowledgeFiles.length > 0) {
|
|
308
737
|
process.stdout.write(`\nIdentity references existing knowledge: ${ui.style.cyan(result.knowledgeFiles.join(", "))}\n`);
|
|
309
738
|
}
|
|
739
|
+
if (recipe !== undefined) {
|
|
740
|
+
process.stdout.write(`\n${ui.style.bold("Recipe:")} ${ui.style.cyan(recipe.id)} ${ui.style.dim(`(risk: ${recipe.riskLevel})`)}\n`);
|
|
741
|
+
if (recipe.envExample !== undefined) {
|
|
742
|
+
process.stdout.write(ui.style.dim("Fill the secret placeholders in .env.example, then copy it to .env.\n"));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function printSecretsChecklist(secrets) {
|
|
747
|
+
process.stdout.write("\n" + ui.heading("Secrets checklist"));
|
|
748
|
+
if (secrets.length === 0) {
|
|
749
|
+
process.stdout.write(ui.style.dim("No secret recipe inputs were prompted. Review .env.example if the recipe created one.\n"));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
process.stdout.write(ui.style.dim("Copy .env.example to .env, then fill these variables. Secret values were not prompted or written to JSON.\n"));
|
|
753
|
+
for (const secret of secrets) {
|
|
754
|
+
const key = secret.envVar ?? secret.id;
|
|
755
|
+
process.stdout.write(` ${ui.style.bold(key)} ${ui.style.dim(`- ${secret.label}: ${secret.description}`)}\n`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function printNextSteps(configPath, recipe) {
|
|
759
|
+
const validateCmd = recipe === undefined ? "mono-agent validate" : `mono-agent validate --recipe ${recipe.id}`;
|
|
310
760
|
process.stdout.write("\n" +
|
|
311
761
|
ui.heading("Next steps") +
|
|
312
|
-
` ${ui.style.bold("1.")} Edit ${
|
|
313
|
-
` ${ui.style.bold("2.")}
|
|
762
|
+
` ${ui.style.bold("1.")} Edit ${configPath} ${ui.style.dim("(model, channels, skills, memory, sandbox)")}\n` +
|
|
763
|
+
` ${ui.style.bold("2.")} ${validateCmd}\n` +
|
|
314
764
|
` ${ui.style.bold("3.")} mono-agent start\n`);
|
|
315
|
-
return 0;
|
|
316
765
|
}
|
|
317
766
|
async function runInstallSkill(args) {
|
|
318
767
|
let result;
|
|
@@ -332,20 +781,219 @@ async function runInstallSkill(args) {
|
|
|
332
781
|
return 0;
|
|
333
782
|
}
|
|
334
783
|
async function runValidate(args) {
|
|
335
|
-
const
|
|
784
|
+
const context = resolveValidateContext(args, process.cwd());
|
|
336
785
|
const report = await validateMonoAgentFolder({
|
|
337
786
|
env: process.env,
|
|
338
|
-
cwd,
|
|
339
|
-
configPath:
|
|
787
|
+
cwd: context.cwd,
|
|
788
|
+
configPath: context.configPath,
|
|
789
|
+
allowFilesystemWrites: context.allowFilesystemWrites,
|
|
340
790
|
});
|
|
341
791
|
for (const section of report.sections) {
|
|
342
792
|
process.stdout.write(formatSection(section));
|
|
343
793
|
}
|
|
794
|
+
if (args.recipe !== undefined) {
|
|
795
|
+
const recipe = findRecipe(args.recipe);
|
|
796
|
+
if (recipe === undefined) {
|
|
797
|
+
process.stderr.write(ui.errorLine(`Unknown recipe \`${args.recipe}\`.`));
|
|
798
|
+
process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
|
|
799
|
+
return 1;
|
|
800
|
+
}
|
|
801
|
+
process.stdout.write(renderRecipeCompleteness(recipe, report));
|
|
802
|
+
}
|
|
344
803
|
process.stdout.write(report.ok
|
|
345
|
-
? `\n${ui.style.green("✓ Config is ready to start.")}\n`
|
|
804
|
+
? `\n${ui.style.green("✓ Config is ready to start.")}\n${ui.style.dim("Run `mono-agent config` for the full field-by-field view.")}\n`
|
|
346
805
|
: `\n${ui.hint("Fix the errors above, then re-run mono-agent validate.")}`);
|
|
806
|
+
process.stdout.write(ui.style.dim("Core sections activate by presence; channels need `enabled: true` — see docs/config (How sections activate).\n"));
|
|
347
807
|
return report.ok ? 0 : 1;
|
|
348
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Capability-aware recipe check: for each capability the recipe promises, report
|
|
811
|
+
* whether the matching doctor section has reached the expected status. `waiting`
|
|
812
|
+
* stays non-fatal (it never changes the validate exit code) but is surfaced as
|
|
813
|
+
* "selected recipe incomplete" so the operator knows what is left to wire up.
|
|
814
|
+
*/
|
|
815
|
+
function renderRecipeCompleteness(recipe, report) {
|
|
816
|
+
let out = "\n" + ui.heading(`Recipe: ${recipe.id}`);
|
|
817
|
+
let incomplete = 0;
|
|
818
|
+
for (const expectation of recipe.validateExpectations) {
|
|
819
|
+
const section = report.sections.find((entry) => entry.id === expectation.sectionId);
|
|
820
|
+
const status = section?.status ?? "missing";
|
|
821
|
+
const met = status === expectation.mustBe;
|
|
822
|
+
if (!met) {
|
|
823
|
+
incomplete += 1;
|
|
824
|
+
}
|
|
825
|
+
const badge = met ? ui.badge("ok") : ui.badge(status === "error" ? "error" : "waiting");
|
|
826
|
+
out += `${badge}${ui.style.bold(expectation.sectionId)} ${ui.style.dim(`(${status}, expected ${expectation.mustBe})`)}\n`;
|
|
827
|
+
if (!met && expectation.note !== undefined) {
|
|
828
|
+
out += ` ${ui.style.dim(expectation.note)}\n`;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
out += incomplete === 0
|
|
832
|
+
? `${ui.style.green(`✓ Recipe ${recipe.id} is fully configured.`)}\n`
|
|
833
|
+
: ui.style.yellow(`⚠ Selected recipe incomplete: ${incomplete} capability(ies) not yet live.\n`);
|
|
834
|
+
return out;
|
|
835
|
+
}
|
|
836
|
+
function riskColor(risk) {
|
|
837
|
+
if (risk === "high") {
|
|
838
|
+
return ui.style.red(risk);
|
|
839
|
+
}
|
|
840
|
+
if (risk === "medium") {
|
|
841
|
+
return ui.style.yellow(risk);
|
|
842
|
+
}
|
|
843
|
+
return ui.style.green(risk);
|
|
844
|
+
}
|
|
845
|
+
/** `mono-agent recipes list` — one line per recipe. */
|
|
846
|
+
export function renderRecipeList() {
|
|
847
|
+
let out = ui.banner("mono-agent", "recipes") + "\n";
|
|
848
|
+
for (const recipe of RECIPE_CATALOG) {
|
|
849
|
+
out += `${ui.style.bold(ui.style.cyan(recipe.id))} ${ui.style.dim(`[${riskColor(recipe.riskLevel)}]`)}\n`;
|
|
850
|
+
out += ` ${recipe.title}\n`;
|
|
851
|
+
out += ` ${ui.style.dim(recipe.tags.join(", "))}\n`;
|
|
852
|
+
}
|
|
853
|
+
out += "\n" + ui.style.dim("Scaffold one with: mono-agent init --recipe <id> [--dry-run] [--with slack,cron]\n");
|
|
854
|
+
return out;
|
|
855
|
+
}
|
|
856
|
+
/** `mono-agent recipes show <id>` — description, generated JSON, env example, checklist. */
|
|
857
|
+
export function renderRecipeShow(recipe) {
|
|
858
|
+
const inputs = resolveRecipeInputs(recipe);
|
|
859
|
+
let out = ui.banner("mono-agent", `recipe: ${recipe.id}`) + "\n";
|
|
860
|
+
out += `${ui.style.bold(recipe.title)} ${ui.style.dim(`(risk: ${riskColor(recipe.riskLevel)})`)}\n`;
|
|
861
|
+
out += `${recipe.description}\n`;
|
|
862
|
+
if (recipe.playbook !== undefined) {
|
|
863
|
+
out += ui.style.dim(`Playbook: docs/playbooks/${recipe.playbook}\n`);
|
|
864
|
+
}
|
|
865
|
+
out += "\n" + ui.heading("Generated mono-agent.config.json");
|
|
866
|
+
out += JSON.stringify(recipe.config(inputs), null, 2) + "\n";
|
|
867
|
+
const envExample = recipe.envExample?.(inputs);
|
|
868
|
+
if (envExample !== undefined && envExample.trim().length > 0) {
|
|
869
|
+
out += "\n" + ui.heading(".env.example");
|
|
870
|
+
out += envExample.endsWith("\n") ? envExample : envExample + "\n";
|
|
871
|
+
}
|
|
872
|
+
const files = recipe.files?.(inputs) ?? [];
|
|
873
|
+
if (files.length > 0) {
|
|
874
|
+
out += "\n" + ui.heading("Scaffolded files");
|
|
875
|
+
for (const file of files) {
|
|
876
|
+
out += ` ${ui.style.cyan(file.path)}\n`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (recipe.validateExpectations.length > 0) {
|
|
880
|
+
out += "\n" + ui.heading("Follow-up checklist");
|
|
881
|
+
for (const expectation of recipe.validateExpectations) {
|
|
882
|
+
const note = expectation.note === undefined ? "" : ` — ${expectation.note}`;
|
|
883
|
+
out += ` ${ui.style.gray("•")} ${expectation.sectionId} ${ui.style.dim(`must be ${expectation.mustBe}`)}${ui.style.dim(note)}\n`;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return out;
|
|
887
|
+
}
|
|
888
|
+
/** Dispatch `mono-agent recipes list|show <id>`. */
|
|
889
|
+
function runRecipes(args) {
|
|
890
|
+
const [sub, id] = args.positionals;
|
|
891
|
+
if (sub === undefined || sub === "list") {
|
|
892
|
+
process.stdout.write(renderRecipeList());
|
|
893
|
+
return 0;
|
|
894
|
+
}
|
|
895
|
+
if (sub === "show") {
|
|
896
|
+
if (id === undefined) {
|
|
897
|
+
process.stderr.write(ui.errorLine("Usage: mono-agent recipes show <id>."));
|
|
898
|
+
process.stderr.write(ui.hint(`Available: ${recipeIds().join(", ")}.`));
|
|
899
|
+
return 2;
|
|
900
|
+
}
|
|
901
|
+
const recipe = findRecipe(id);
|
|
902
|
+
if (recipe === undefined) {
|
|
903
|
+
process.stderr.write(ui.errorLine(`Unknown recipe \`${id}\`.`));
|
|
904
|
+
process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
|
|
905
|
+
return 1;
|
|
906
|
+
}
|
|
907
|
+
process.stdout.write(renderRecipeShow(recipe));
|
|
908
|
+
return 0;
|
|
909
|
+
}
|
|
910
|
+
process.stderr.write(ui.errorLine(`Unknown recipes subcommand \`${sub}\`. Expected list or show.`));
|
|
911
|
+
return 2;
|
|
912
|
+
}
|
|
913
|
+
const SOURCE_TAG = {
|
|
914
|
+
env: ui.style.green("[env]"),
|
|
915
|
+
json: ui.style.cyan("[json]"),
|
|
916
|
+
default: ui.style.dim("[default]"),
|
|
917
|
+
};
|
|
918
|
+
/**
|
|
919
|
+
* Render the complete, source-annotated config view: every core section and
|
|
920
|
+
* field with its resolved value and whether it came from an env var, the JSON
|
|
921
|
+
* file, or the built-in default. This is the single discovery surface that
|
|
922
|
+
* replaced the old partial config panes.
|
|
923
|
+
*/
|
|
924
|
+
export function renderConfigView(sections) {
|
|
925
|
+
let out = "";
|
|
926
|
+
for (const section of sections) {
|
|
927
|
+
const badgeStatus = section.status === "active" ? "ok" : "disabled";
|
|
928
|
+
out += `${ui.badge(badgeStatus)}${ui.style.bold(section.label)}\n`;
|
|
929
|
+
const width = section.fields.reduce((max, field) => Math.max(max, field.label.length), 0);
|
|
930
|
+
for (const field of section.fields) {
|
|
931
|
+
const tag = SOURCE_TAG[field.source];
|
|
932
|
+
const lock = field.redacted === true ? ` ${ui.style.dim("(secret)")}` : "";
|
|
933
|
+
const defaultRestatement = field.restatesDefault === true ? ` ${ui.style.dim("(same as default)")}` : "";
|
|
934
|
+
out += ` ${ui.style.gray(field.label.padEnd(width))} ${field.value}${lock}${defaultRestatement} ${tag}\n`;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* `mono-agent config`: print the resolved configuration field-by-field with the
|
|
941
|
+
* source (env / json / default) of every value, then the channel summary. Read
|
|
942
|
+
* only — edits go in mono-agent.config.json and take effect on the next restart.
|
|
943
|
+
*/
|
|
944
|
+
async function runConfig(args) {
|
|
945
|
+
const cwd = process.cwd();
|
|
946
|
+
const configPath = resolve(cwd, args.configPath ?? "mono-agent.config.json");
|
|
947
|
+
const env = process.env;
|
|
948
|
+
const jsonResult = await readMonoAgentConfigJson(configPath);
|
|
949
|
+
let config;
|
|
950
|
+
try {
|
|
951
|
+
config = await loadAppCoreConfig({ env, cwd, configPath });
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
if (isAppCoreConfigError(error)) {
|
|
955
|
+
process.stderr.write(ui.errorLine(error.message));
|
|
956
|
+
if (jsonResult.missing) {
|
|
957
|
+
process.stderr.write(ui.hint(`No mono-agent config found at ${configPath}. Run \`mono-agent init\` to scaffold one.`));
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
process.stderr.write(ui.hint("Fix the config above, then re-run `mono-agent config`."));
|
|
961
|
+
}
|
|
962
|
+
return 1;
|
|
963
|
+
}
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
966
|
+
const sections = buildMonoAgentConfigView({
|
|
967
|
+
redacted: redactMonoAgentConfig(config),
|
|
968
|
+
json: jsonResult.json,
|
|
969
|
+
env,
|
|
970
|
+
});
|
|
971
|
+
const drivers = await resolveChannelDrivers({ env, cwd, configPath });
|
|
972
|
+
const channelViews = await collectChannelConfigViews(drivers, { env, cwd, configPath });
|
|
973
|
+
process.stdout.write(ui.banner("mono-agent", "resolved config") + "\n");
|
|
974
|
+
process.stdout.write(renderConfigView(sections));
|
|
975
|
+
if (channelViews.length > 0) {
|
|
976
|
+
process.stdout.write("\n" + ui.heading("Channels"));
|
|
977
|
+
process.stdout.write(renderConfigView(channelViews));
|
|
978
|
+
}
|
|
979
|
+
for (const warning of [
|
|
980
|
+
...findUnknownAppConfigWarnings(jsonResult.json),
|
|
981
|
+
...findJsonSecretConfigWarnings([...sections, ...channelViews]),
|
|
982
|
+
...findRemovedConfigWarnings({ json: jsonResult.json, env }),
|
|
983
|
+
]) {
|
|
984
|
+
process.stdout.write(`${ui.style.yellow(warning)}\n`);
|
|
985
|
+
}
|
|
986
|
+
const report = await validateMonoAgentFolder({ env, cwd, configPath, liveness: false, drivers });
|
|
987
|
+
const channels = report.sections.filter((section) => section.id.startsWith("channel:"));
|
|
988
|
+
if (channels.length > 0) {
|
|
989
|
+
process.stdout.write("\n" + ui.heading("Channel status"));
|
|
990
|
+
for (const section of channels) {
|
|
991
|
+
process.stdout.write(formatSection(section));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
process.stdout.write("\n" + ui.style.dim("Source precedence: [env] > [json] > [default]. Edit mono-agent.config.json and run `mono-agent restart` to apply.") + "\n");
|
|
995
|
+
return 0;
|
|
996
|
+
}
|
|
349
997
|
/** Render one validation section: a status badge, a bold label, and its details. */
|
|
350
998
|
function formatSection(section) {
|
|
351
999
|
let out = `${ui.badge(section.status)}${ui.style.bold(section.label)}\n`;
|
|
@@ -358,7 +1006,7 @@ function colorDetail(status, detail) {
|
|
|
358
1006
|
if (status === "error") {
|
|
359
1007
|
return ui.style.red(detail);
|
|
360
1008
|
}
|
|
361
|
-
if (detail.startsWith("[WARN]")) {
|
|
1009
|
+
if (detail.startsWith("[WARN]") || detail.startsWith("WARNING:")) {
|
|
362
1010
|
return ui.style.yellow(detail);
|
|
363
1011
|
}
|
|
364
1012
|
return ui.style.dim(detail);
|
|
@@ -429,7 +1077,7 @@ async function runForeground(args) {
|
|
|
429
1077
|
...(args.configPath === undefined ? {} : { configPath: args.configPath }),
|
|
430
1078
|
logger: consoleLogger(),
|
|
431
1079
|
});
|
|
432
|
-
printAppStatus(app);
|
|
1080
|
+
await printAppStatus(app);
|
|
433
1081
|
// Block until a shutdown signal. Returning here (the old behavior) let the
|
|
434
1082
|
// process exit immediately whenever no channel owned a live handle — e.g. a
|
|
435
1083
|
// traceability-only config, now that the operator console is retired and the
|
|
@@ -508,7 +1156,7 @@ function requireDarwin(command) {
|
|
|
508
1156
|
process.stderr.write(ui.hint("Run `mono-agent start --foreground` to run in the foreground on this platform."));
|
|
509
1157
|
return 1;
|
|
510
1158
|
}
|
|
511
|
-
export function printAppStatus(app) {
|
|
1159
|
+
export async function printAppStatus(app, options = {}) {
|
|
512
1160
|
const trace = app.traceabilityStatus;
|
|
513
1161
|
process.stdout.write(ui.rule("instance"));
|
|
514
1162
|
process.stdout.write(ui.keyValue([
|
|
@@ -519,6 +1167,8 @@ export function printAppStatus(app) {
|
|
|
519
1167
|
],
|
|
520
1168
|
], 2));
|
|
521
1169
|
const artifactDir = app.traceabilityStatus.kind === "running" ? app.traceabilityStatus.artifactDir : undefined;
|
|
1170
|
+
process.stdout.write(ui.rule("sandbox"));
|
|
1171
|
+
process.stdout.write(` ${describeSandboxStatus(app.sandboxStatus)}\n`);
|
|
522
1172
|
process.stdout.write(ui.rule("observability"));
|
|
523
1173
|
process.stdout.write(` ${describeExporter(app.exporterStatus, artifactDir)}\n`);
|
|
524
1174
|
const channels = [...app.channelStatuses()];
|
|
@@ -528,6 +1178,43 @@ export function printAppStatus(app) {
|
|
|
528
1178
|
process.stdout.write(` ${ui.channelBadge(status.kind)}${ui.style.bold(id.padEnd(11))} ${describeChannelStatus(status)}\n`);
|
|
529
1179
|
}
|
|
530
1180
|
}
|
|
1181
|
+
await writeAppRunsHealthDetail(app, options);
|
|
1182
|
+
}
|
|
1183
|
+
async function writeAppRunsHealthDetail(app, options) {
|
|
1184
|
+
const artifactDir = app.traceabilityStatus.kind === "running" ? app.traceabilityStatus.artifactDir : undefined;
|
|
1185
|
+
if (artifactDir === undefined || artifactDir.trim().length === 0) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const reader = options.listRecordedRuns ?? listRecordedRuns;
|
|
1189
|
+
let result;
|
|
1190
|
+
try {
|
|
1191
|
+
result = await reader({ artifactDir, maxRuns: RUNS_HEALTH_MAX_RUNS, scope: "agent" });
|
|
1192
|
+
}
|
|
1193
|
+
catch (error) {
|
|
1194
|
+
result = {
|
|
1195
|
+
totalRuns: 0,
|
|
1196
|
+
runs: [],
|
|
1197
|
+
warnings: [`Unable to read run summaries: ${reasonOf(error)}`],
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const display = buildRunsHealthDisplay({
|
|
1201
|
+
artifactDir,
|
|
1202
|
+
totalRuns: result.totalRuns,
|
|
1203
|
+
runs: result.runs,
|
|
1204
|
+
warnings: result.warnings,
|
|
1205
|
+
includeSelectedSkills: true,
|
|
1206
|
+
runOwnerAlive: true,
|
|
1207
|
+
maxRuns: RUNS_HEALTH_MAX_RUNS,
|
|
1208
|
+
...(app.selectedSkills === undefined ? {} : { selectedSkills: app.selectedSkills }),
|
|
1209
|
+
...(options.nowMs === undefined ? {} : { nowMs: options.nowMs }),
|
|
1210
|
+
});
|
|
1211
|
+
process.stdout.write(ui.rule("runs health"));
|
|
1212
|
+
for (const detail of display.details) {
|
|
1213
|
+
process.stdout.write(` ${detail}\n`);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function reasonOf(error) {
|
|
1217
|
+
return error instanceof Error ? error.message : String(error);
|
|
531
1218
|
}
|
|
532
1219
|
function describeExporter(status, artifactDir) {
|
|
533
1220
|
if (status.kind !== "configured") {
|
|
@@ -539,7 +1226,7 @@ function describeExporter(status, artifactDir) {
|
|
|
539
1226
|
parts.push(`app ${appUrl}`);
|
|
540
1227
|
}
|
|
541
1228
|
if (status.includeSensitiveData) {
|
|
542
|
-
parts.push(
|
|
1229
|
+
parts.push(ui.style.yellow(describeSensitiveDataExportWarning(status.endpoint)));
|
|
543
1230
|
}
|
|
544
1231
|
if (status.lastWarning !== undefined) {
|
|
545
1232
|
parts.push(`last warning: ${status.lastWarning}`);
|
|
@@ -552,15 +1239,53 @@ function describeExporter(status, artifactDir) {
|
|
|
552
1239
|
: `JSONL artifacts remain local at ${artifactDir}`);
|
|
553
1240
|
return parts.join("; ");
|
|
554
1241
|
}
|
|
555
|
-
function
|
|
1242
|
+
function describeSandboxStatus(status) {
|
|
1243
|
+
const engineAvailability = status.engineAvailable === true
|
|
1244
|
+
? "present"
|
|
1245
|
+
: status.engineAvailable === false
|
|
1246
|
+
? "absent"
|
|
1247
|
+
: "not checked";
|
|
1248
|
+
const parts = [
|
|
1249
|
+
`effective: ${status.effective}`,
|
|
1250
|
+
`engine: ${status.engine ?? "none"} (${engineAvailability})`,
|
|
1251
|
+
...(status.fallback === undefined ? [] : [`fallback: ${status.fallback}`]),
|
|
1252
|
+
`fallback active: ${status.fallbackActive ? "yes" : "no"}`,
|
|
1253
|
+
status.detail,
|
|
1254
|
+
];
|
|
1255
|
+
const warning = status.warning ?? sandboxEffectiveStateWarning(status);
|
|
1256
|
+
if (warning !== undefined) {
|
|
1257
|
+
parts.push(ui.style.yellow(warning));
|
|
1258
|
+
}
|
|
1259
|
+
return parts.join("; ");
|
|
1260
|
+
}
|
|
1261
|
+
export function describeChannelStatus(status) {
|
|
556
1262
|
if (status.kind === "running") {
|
|
557
1263
|
const facts = Object.entries(status.summary)
|
|
558
|
-
.map(([key, value]) => `${key}=${
|
|
1264
|
+
.map(([key, value]) => `${key}=${formatChannelFactValue(value)}`)
|
|
559
1265
|
.join(" ");
|
|
560
1266
|
return facts.length === 0 ? "running" : `running (${facts})`;
|
|
561
1267
|
}
|
|
562
1268
|
return `${status.kind}: ${status.reason}`;
|
|
563
1269
|
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Render a channel-status summary value for the human-readable status line.
|
|
1272
|
+
* Objects and arrays are expanded to `key: value` pairs instead of the useless
|
|
1273
|
+
* `[object Object]` that a bare `String()` produced (E4: the webhook summary's
|
|
1274
|
+
* `invokeUrls` map printed `invokeUrls=[object Object]`).
|
|
1275
|
+
*/
|
|
1276
|
+
function formatChannelFactValue(value) {
|
|
1277
|
+
if (value === null || typeof value !== "object") {
|
|
1278
|
+
return String(value);
|
|
1279
|
+
}
|
|
1280
|
+
if (Array.isArray(value)) {
|
|
1281
|
+
return `[${value.map(formatChannelFactValue).join(", ")}]`;
|
|
1282
|
+
}
|
|
1283
|
+
const entries = Object.entries(value);
|
|
1284
|
+
if (entries.length === 0) {
|
|
1285
|
+
return "{}";
|
|
1286
|
+
}
|
|
1287
|
+
return `{${entries.map(([key, inner]) => `${key}: ${formatChannelFactValue(inner)}`).join(", ")}}`;
|
|
1288
|
+
}
|
|
564
1289
|
/**
|
|
565
1290
|
* Block the foreground process until SIGINT/SIGTERM, then stop the app and
|
|
566
1291
|
* resolve the exit code. A referenced no-op timer owns the event loop so the
|