@mono-agent/agent-app 0.4.0 → 0.4.1

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