@mono-agent/agent-app 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +9 -5
  2. package/dist/adapter-send-tools-main.d.ts +3 -0
  3. package/dist/adapter-send-tools-main.d.ts.map +1 -0
  4. package/dist/adapter-send-tools-main.js +20 -0
  5. package/dist/adapter-send-tools-main.js.map +1 -0
  6. package/dist/adapter-send-tools.d.ts +59 -0
  7. package/dist/adapter-send-tools.d.ts.map +1 -0
  8. package/dist/adapter-send-tools.js +254 -0
  9. package/dist/adapter-send-tools.js.map +1 -0
  10. package/dist/app-config.d.ts +40 -17
  11. package/dist/app-config.d.ts.map +1 -1
  12. package/dist/app-config.js +188 -68
  13. package/dist/app-config.js.map +1 -1
  14. package/dist/app.d.ts +41 -20
  15. package/dist/app.d.ts.map +1 -1
  16. package/dist/app.js +349 -87
  17. package/dist/app.js.map +1 -1
  18. package/dist/backfill.d.ts +79 -0
  19. package/dist/backfill.d.ts.map +1 -0
  20. package/dist/backfill.js +246 -0
  21. package/dist/backfill.js.map +1 -0
  22. package/dist/background.d.ts +0 -4
  23. package/dist/background.d.ts.map +1 -1
  24. package/dist/background.js +78 -50
  25. package/dist/background.js.map +1 -1
  26. package/dist/channels.d.ts +7 -0
  27. package/dist/channels.d.ts.map +1 -1
  28. package/dist/channels.js +26 -3
  29. package/dist/channels.js.map +1 -1
  30. package/dist/cli.d.ts +53 -4
  31. package/dist/cli.d.ts.map +1 -1
  32. package/dist/cli.js +317 -113
  33. package/dist/cli.js.map +1 -1
  34. package/dist/doctor.d.ts +8 -0
  35. package/dist/doctor.d.ts.map +1 -1
  36. package/dist/doctor.js +236 -30
  37. package/dist/doctor.js.map +1 -1
  38. package/dist/index.d.ts +8 -4
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/init.d.ts +1 -1
  43. package/dist/init.d.ts.map +1 -1
  44. package/dist/init.js +1 -1
  45. package/dist/init.js.map +1 -1
  46. package/dist/launchd.d.ts +0 -2
  47. package/dist/launchd.d.ts.map +1 -1
  48. package/dist/launchd.js +0 -2
  49. package/dist/launchd.js.map +1 -1
  50. package/dist/memory-recall-main.d.ts +3 -0
  51. package/dist/memory-recall-main.d.ts.map +1 -0
  52. package/dist/memory-recall-main.js +30 -0
  53. package/dist/memory-recall-main.js.map +1 -0
  54. package/dist/memory-recall.d.ts +111 -0
  55. package/dist/memory-recall.d.ts.map +1 -0
  56. package/dist/memory-recall.js +275 -0
  57. package/dist/memory-recall.js.map +1 -0
  58. package/dist/memory-rituals.d.ts +51 -0
  59. package/dist/memory-rituals.d.ts.map +1 -0
  60. package/dist/memory-rituals.js +228 -0
  61. package/dist/memory-rituals.js.map +1 -0
  62. package/dist/sessions.d.ts +21 -0
  63. package/dist/sessions.d.ts.map +1 -0
  64. package/dist/sessions.js +45 -0
  65. package/dist/sessions.js.map +1 -0
  66. package/dist/ui.d.ts +54 -0
  67. package/dist/ui.d.ts.map +1 -0
  68. package/dist/ui.js +153 -0
  69. package/dist/ui.js.map +1 -0
  70. package/package.json +22 -17
  71. package/skills/mono-agent-composer/SKILL.md +7 -6
  72. package/skills/mono-agent-composer/references/config-blueprint.md +53 -22
  73. package/skills/mono-agent-composer/references/discovery-questions.md +121 -10
  74. package/skills/mono-agent-composer/references/feature-coverage.md +15 -12
  75. package/skills/mono-agent-composer/references/package-map.md +9 -11
  76. package/skills/mono-agent-composer/references/validation.md +5 -4
package/dist/app.js CHANGED
@@ -1,60 +1,23 @@
1
1
  import { resolve } from "node:path";
2
- import { startOperatorConsole } from "@mono-agent/operator-console";
3
- import { createConfiguredAgentResponder, createConfiguredAgentRuntime } from "@mono-agent/agent-host";
2
+ import { createConfiguredAgentResponder, createConfiguredAgentRuntime, createConfiguredMemory, } from "@mono-agent/agent-host";
4
3
  import { registerTraceSource } from "@mono-agent/observability";
5
- import { isAppCoreConfigError, loadAppCoreConfig, MONO_AGENT_APP_FIELD_GROUPS, resolveAppArtifactDir, resolveAppConsoleSettings, resolveAppTraceHeartbeatMs, resolveAppTraceRegistryDir, resolveAppTraceSourceId, resolveAppTraceSourceLabel, resolveAppTraceStaleAfterMs, } from "./app-config.js";
4
+ import { isAppCoreConfigError, loadAppCoreConfig, resolveAppArtifactDir, resolveAppObservabilityExporters, resolveAppTraceHeartbeatMs, resolveAppTraceRegistryDir, resolveAppTraceSourceId, resolveAppTraceSourceLabel, resolveAppTraceStaleAfterMs, } from "./app-config.js";
6
5
  import { defaultChannelDrivers } from "./channels.js";
6
+ import { adapterSendToolNames, createAdapterSendToolsRuntimeExtension, resolveAdapterSendToolsSettings, } from "./adapter-send-tools.js";
7
+ import { startMemoryRituals } from "./memory-rituals.js";
8
+ import { createMemoryRecallRuntimeExtension, resolveMemoryRecallSettings, } from "./memory-recall.js";
7
9
  /**
8
- * Starts a config-first mono-agent host in `cwd`: operator console first (so
9
- * an incomplete config can be fixed in the browser), then traceability, then
10
- * every configured channel in parallel. Channels with incomplete config report
11
- * `waiting_for_config` instead of blocking the rest.
10
+ * Starts a config-first mono-agent host in `cwd`: traceability first, then every
11
+ * configured channel in parallel. Channels with incomplete config report
12
+ * `waiting_for_config` instead of blocking the rest. The host runs headless;
13
+ * config changes take effect on the next restart.
12
14
  */
13
15
  export async function startMonoAgentApp(options = {}) {
14
16
  const cwd = resolve(options.cwd ?? process.cwd());
15
17
  const configPath = resolve(cwd, options.configPath ?? "mono-agent.config.json");
16
18
  const env = options.env ?? process.env;
17
19
  const drivers = options.drivers ?? defaultChannelDrivers();
18
- const input = { env, cwd, configPath };
19
- let controller;
20
- let consoleServer;
21
- const consoleSettings = await resolveAppConsoleSettings(input);
22
- const consolePort = options.operatorConsolePort ?? consoleSettings.port;
23
- if (options.operatorConsole !== false && consoleSettings.enabled) {
24
- const consoleFactory = options.operatorConsoleFactory ?? startOperatorConsole;
25
- consoleServer = await consoleFactory({
26
- configPath,
27
- cwd,
28
- fieldGroups: options.fieldGroups ?? MONO_AGENT_APP_FIELD_GROUPS,
29
- observability: {
30
- artifactDir: () => resolveAppArtifactDir(input),
31
- maxRuns: 100,
32
- maxEventsPerRun: 750,
33
- },
34
- traceability: {
35
- registryDir: () => resolveAppTraceRegistryDir(input),
36
- staleAfterMs: () => resolveAppTraceStaleAfterMs(input),
37
- maxRuns: 100,
38
- maxEventsPerRun: 750,
39
- },
40
- applyConfigWrite: async () => {
41
- if (controller === undefined) {
42
- return {
43
- kind: "failed",
44
- message: "Mono agent app lifecycle is not ready to apply config changes.",
45
- transports: [],
46
- };
47
- }
48
- return await controller.applyConfigChange("operator-console-write");
49
- },
50
- ...(consolePort === undefined ? {} : { port: consolePort }),
51
- log: (event) => {
52
- logOperatorConsoleEvent(options.logger, event);
53
- },
54
- });
55
- }
56
- controller = new MonoAgentAppController({
57
- consoleServer,
20
+ const controller = new MonoAgentAppController({
58
21
  cwd,
59
22
  configPath,
60
23
  env,
@@ -64,14 +27,14 @@ export async function startMonoAgentApp(options = {}) {
64
27
  ...(options.traceDefaults === undefined ? {} : { traceDefaults: options.traceDefaults }),
65
28
  });
66
29
  await controller.startTraceability("startup");
30
+ await controller.startExporters("startup");
67
31
  await Promise.all(drivers.map((driver) => controller?.startChannelIfConfigured(driver.id, "startup")));
32
+ await controller.startMemoryRitualsIfConfigured("startup");
68
33
  await controller.refreshTraceSource("startup-complete");
69
34
  return controller;
70
35
  }
71
36
  class MonoAgentAppController {
72
- operatorConsole;
73
37
  configPath;
74
- consoleServer;
75
38
  cwd;
76
39
  env;
77
40
  drivers;
@@ -87,11 +50,21 @@ class MonoAgentAppController {
87
50
  kind: "disabled",
88
51
  reason: "Traceability has not started yet.",
89
52
  };
53
+ exporterStatusValue = {
54
+ kind: "disabled",
55
+ reason: "No observability exporter configured.",
56
+ };
57
+ /** The exporter the responder threads into agent-host (first configured exporter). */
58
+ resolvedExporter;
90
59
  traceSource;
60
+ memoryRituals;
61
+ // One shared memory store across all channel responders + the ritual scheduler, so there is a single
62
+ // memory.db handle (not one per channel plus one for rituals). Rebuilt on config reload, closed on stop.
63
+ sharedMemory = undefined;
64
+ sharedMemoryBuilt = false;
91
65
  configApplyTail = Promise.resolve();
92
66
  stopped = false;
93
67
  constructor(input) {
94
- this.consoleServer = input.consoleServer;
95
68
  this.cwd = input.cwd;
96
69
  this.configPath = input.configPath;
97
70
  this.env = input.env;
@@ -106,18 +79,13 @@ class MonoAgentAppController {
106
79
  reason: `${driver.label} has not been configured yet.`,
107
80
  });
108
81
  }
109
- this.operatorConsole = input.consoleServer === undefined
110
- ? undefined
111
- : {
112
- url: input.consoleServer.url,
113
- appUrl: `${input.consoleServer.url}/?t=${input.consoleServer.token}`,
114
- token: input.consoleServer.token,
115
- configPath: input.configPath,
116
- };
117
82
  }
118
83
  get traceabilityStatus() {
119
84
  return this.traceabilityStatusValue;
120
85
  }
86
+ get exporterStatus() {
87
+ return this.exporterStatusValue;
88
+ }
121
89
  channelStatus(id) {
122
90
  const status = this.statuses.get(id);
123
91
  if (status === undefined) {
@@ -140,9 +108,13 @@ class MonoAgentAppController {
140
108
  for (const driver of this.drivers) {
141
109
  await this.stopChannel(driver.id, `${reason}:reload`);
142
110
  }
111
+ this.stopMemoryRituals();
112
+ await this.resetSharedMemory();
143
113
  await this.stopTraceSource(`${reason}:reload`);
144
114
  await this.startTraceability(reason);
115
+ await this.startExporters(reason);
145
116
  await Promise.all(this.drivers.map((driver) => this.startChannelIfConfigured(driver.id, reason)));
117
+ await this.startMemoryRitualsIfConfigured(reason);
146
118
  await this.refreshTraceSource(`${reason}:complete`);
147
119
  return this.applyResult();
148
120
  };
@@ -156,11 +128,6 @@ class MonoAgentAppController {
156
128
  return this.channelStatus(id);
157
129
  }
158
130
  if (this.running.has(id)) {
159
- if (reason === "operator-console-write") {
160
- this.logger?.info?.(`${driver.label} is already running; restart the app to apply later config changes.`, {
161
- status: "running",
162
- });
163
- }
164
131
  await this.refreshTraceSource(reason);
165
132
  return this.channelStatus(id);
166
133
  }
@@ -210,6 +177,63 @@ class MonoAgentAppController {
210
177
  }
211
178
  return this.traceabilityStatusValue;
212
179
  }
180
+ /**
181
+ * Resolve the configured observability exporter(s) and publish the export
182
+ * status. No reachability probe runs here — Phoenix may start after the agent,
183
+ * so an unreachable endpoint must not block startup (that probe runs in
184
+ * `validate`). A present-but-invalid exporter config surfaces as `failed`.
185
+ */
186
+ async startExporters(reason) {
187
+ if (this.stopped) {
188
+ return this.exporterStatusValue;
189
+ }
190
+ const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
191
+ let exporters;
192
+ try {
193
+ exporters = await resolveAppObservabilityExporters(input);
194
+ }
195
+ catch (error) {
196
+ this.resolvedExporter = undefined;
197
+ this.exporterStatusValue = { kind: "failed", reason: reasonOf(error) };
198
+ this.logger?.error?.("Observability exporter config is invalid.", { reason: reasonOf(error) });
199
+ return this.exporterStatusValue;
200
+ }
201
+ const exporter = exporters[0];
202
+ if (exporter === undefined) {
203
+ this.resolvedExporter = undefined;
204
+ this.exporterStatusValue = { kind: "disabled", reason: "No observability exporter configured." };
205
+ return this.exporterStatusValue;
206
+ }
207
+ this.resolvedExporter = exporter;
208
+ this.exporterStatusValue = {
209
+ kind: "configured",
210
+ endpoint: exporter.endpoint,
211
+ includeSensitiveData: exporter.includeSensitiveData ?? false,
212
+ };
213
+ this.logger?.info?.("Observability exporter configured.", {
214
+ reason,
215
+ endpoint: exporter.endpoint,
216
+ includeSensitiveData: exporter.includeSensitiveData ?? false,
217
+ });
218
+ return this.exporterStatusValue;
219
+ }
220
+ /** Record a best-effort export warning so `status` can surface it without failing the run. */
221
+ recordExporterWarning(warning) {
222
+ const current = this.exporterStatusValue;
223
+ if (current.kind !== "configured") {
224
+ return;
225
+ }
226
+ const message = `${warning.phase}: ${warning.message}`;
227
+ // The "fail" phase fires only when export fails on the run-failure path;
228
+ // surface it as lastError so operators can tell it apart from a transient
229
+ // best-effort warning. The run outcome is unchanged either way.
230
+ this.exporterStatusValue =
231
+ warning.phase === "fail" ? { ...current, lastError: message } : { ...current, lastWarning: message };
232
+ this.logger?.warn?.("Observability export warning.", { phase: warning.phase, message: warning.message });
233
+ // Persist to the trace-source manifest so the detached `mono-agent status`
234
+ // (which reads the manifest, not this live object) can surface it too.
235
+ void this.refreshTraceSource("exporter-warning").catch(() => undefined);
236
+ }
213
237
  async refreshTraceSource(reason) {
214
238
  if (this.traceSource === undefined) {
215
239
  return;
@@ -232,12 +256,74 @@ class MonoAgentAppController {
232
256
  for (const driver of this.drivers) {
233
257
  await this.stopChannel(driver.id, "stop");
234
258
  }
259
+ this.stopMemoryRituals();
260
+ await this.resetSharedMemory();
235
261
  await this.stopTraceSource("stop");
236
- await this.consoleServer?.stop();
237
262
  for (const runtime of this.activeRuntimes.splice(0)) {
238
263
  await runtime.disposeAllSessions?.().catch(() => undefined);
239
264
  }
240
265
  }
266
+ async startMemoryRitualsIfConfigured(reason) {
267
+ if (this.stopped) {
268
+ return;
269
+ }
270
+ let coreConfig;
271
+ try {
272
+ const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
273
+ coreConfig = await loadAppCoreConfig(input);
274
+ }
275
+ catch {
276
+ // Config not ready yet — rituals will start on the next applyConfigChange.
277
+ return;
278
+ }
279
+ if (coreConfig.memory?.mode !== "bujo") {
280
+ return;
281
+ }
282
+ const runtime = this.runtime ?? createConfiguredAgentRuntime(coreConfig);
283
+ if (!this.activeRuntimes.includes(runtime)) {
284
+ this.activeRuntimes.push(runtime);
285
+ }
286
+ const store = this.memoryStore(coreConfig, runtime);
287
+ // Duck-type: only bujo-tier BujoMemoryStore has reflect/migrate.
288
+ // Cast through unknown to bypass the MemoryStore contract's type mismatch.
289
+ const storeAsAny = store;
290
+ if (store === undefined ||
291
+ typeof storeAsAny["reflect"] !== "function" ||
292
+ typeof storeAsAny["migrate"] !== "function" ||
293
+ typeof storeAsAny["tier"] !== "function") {
294
+ return;
295
+ }
296
+ const bujoStore = store;
297
+ // `memory.mode` is "bujo", but the store derives the runtime tier from its options: without a
298
+ // `memory.llm` it downgrades to "journal", where startMemoryRituals is a no-op. Don't claim the
299
+ // scheduler started in that case — log an accurate skip instead.
300
+ const tier = bujoStore.tier();
301
+ if (tier !== "bujo") {
302
+ this.logger?.info?.("Memory ritual scheduler skipped — store tier is not bujo (reflect/migrate need a chat LLM).", { reason, tier });
303
+ return;
304
+ }
305
+ this.memoryRituals = startMemoryRituals({
306
+ store: bujoStore,
307
+ ...(coreConfig.memory.reflection !== undefined && { reflection: coreConfig.memory.reflection }),
308
+ ...(coreConfig.memory.migration !== undefined && { migration: coreConfig.memory.migration }),
309
+ ...(this.logger !== undefined && {
310
+ logger: {
311
+ info: (m) => this.logger?.info?.(m),
312
+ warn: (m) => this.logger?.warn?.(m),
313
+ },
314
+ }),
315
+ });
316
+ this.logger?.info?.("Memory ritual scheduler started.", { reason, mode: "bujo" });
317
+ }
318
+ stopMemoryRituals() {
319
+ const rituals = this.memoryRituals;
320
+ if (rituals === undefined) {
321
+ return;
322
+ }
323
+ this.memoryRituals = undefined;
324
+ rituals.stop();
325
+ this.logger?.info?.("Memory ritual scheduler stopped.");
326
+ }
241
327
  async startChannel(driver, reason) {
242
328
  const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
243
329
  let config;
@@ -270,7 +356,16 @@ class MonoAgentAppController {
270
356
  throw error;
271
357
  }
272
358
  try {
273
- const responder = this.buildResponder(coreConfig);
359
+ const responder = await this.buildResponder(coreConfig);
360
+ // The AgentResponder contract has no dispose(), but the configured responder
361
+ // (createAgentResponder) exposes one that tears down the harness + live-session
362
+ // manager. Read it LAZILY (at teardown time) off the `responder` so both the
363
+ // normal stop/reload path AND the onFailure (transport-death) path retire the
364
+ // per-channel harness rather than orphan it, and any wrapper applied to the
365
+ // responder is honored. Duck-typed, consistent with the resetSharedMemory /
366
+ // bujo-store patterns elsewhere in this file.
367
+ const disposeResponder = () => responder.dispose?.();
368
+ const hasDispose = typeof responder.dispose === "function";
274
369
  const runningChannel = await driver.start({
275
370
  config,
276
371
  coreConfig,
@@ -281,9 +376,23 @@ class MonoAgentAppController {
281
376
  this.running.delete(driver.id);
282
377
  this.setStatus(driver.id, { kind: "failed", reason: failureReason });
283
378
  this.logger?.error?.(`${driver.label} channel stopped with an error.`, { reason: failureReason });
379
+ // The running-channel entry (which holds the stop/reload dispose handle)
380
+ // was just deleted, so dispose the responder here too — otherwise a
381
+ // transport death orphans the per-channel harness/live-session manager
382
+ // (the stop/reload path early-returns on the now-missing entry).
383
+ void disposeResponder()?.catch((error) => {
384
+ this.logger?.warn?.(`${driver.label} responder did not dispose cleanly after failure.`, {
385
+ reason: failureReason,
386
+ error: reasonOf(error),
387
+ });
388
+ });
284
389
  },
285
390
  });
286
- this.running.set(driver.id, runningChannel);
391
+ this.running.set(driver.id, {
392
+ ...runningChannel,
393
+ stop: () => runningChannel.stop(),
394
+ ...(hasDispose ? { dispose: () => disposeResponder() ?? Promise.resolve() } : {}),
395
+ });
287
396
  const status = this.setStatus(driver.id, { kind: "running", summary: runningChannel.summary });
288
397
  this.logger?.info?.(`${driver.label} channel is running.`, { reason, ...runningChannel.summary });
289
398
  return status;
@@ -304,6 +413,12 @@ class MonoAgentAppController {
304
413
  await runningChannel.stop().catch((error) => {
305
414
  this.logger?.warn?.(`${driver.label} channel did not stop cleanly.`, { reason, error: reasonOf(error) });
306
415
  });
416
+ // Stop the transport first (so no new turns arrive), then dispose the responder
417
+ // so the harness/live-session manager and warm provider sessions are retired
418
+ // rather than lingering against stale config across a reload.
419
+ await runningChannel.dispose?.().catch((error) => {
420
+ this.logger?.warn?.(`${driver.label} responder did not dispose cleanly.`, { reason, error: reasonOf(error) });
421
+ });
307
422
  if (!this.stopped) {
308
423
  this.setStatus(id, {
309
424
  kind: "waiting_for_config",
@@ -311,12 +426,95 @@ class MonoAgentAppController {
311
426
  });
312
427
  }
313
428
  }
314
- buildResponder(coreConfig) {
429
+ async buildResponder(coreConfig) {
315
430
  const runtime = this.runtime ?? createConfiguredAgentRuntime(coreConfig);
316
431
  if (!this.activeRuntimes.includes(runtime)) {
317
432
  this.activeRuntimes.push(runtime);
318
433
  }
319
- return createConfiguredAgentResponder({ config: coreConfig, runtime });
434
+ const memory = this.memoryStore(coreConfig, runtime);
435
+ const memoryRecall = this.memoryRecallRuntimeOptions(coreConfig);
436
+ const adapterSendTools = await this.adapterSendToolsRuntimeOptions(coreConfig);
437
+ const runtimeOptionsForRequest = composeRuntimeOptionExtensions([memoryRecall, adapterSendTools]);
438
+ const observabilityContext = await this.observabilityContext();
439
+ const responder = createConfiguredAgentResponder({
440
+ config: coreConfig,
441
+ runtime,
442
+ ...(memory !== undefined && { memory }),
443
+ ...(runtimeOptionsForRequest === undefined ? {} : { runtimeOptionsForRequest }),
444
+ // Thread run-identifying context onto exported spans and surface per-run
445
+ // export warnings to `exporterStatus` (agent-host only builds the exporter
446
+ // when config.observability.exporters is non-empty).
447
+ observabilityContext,
448
+ exporterWarn: (warning) => this.recordExporterWarning(warning),
449
+ });
450
+ return responder;
451
+ }
452
+ /**
453
+ * Run-identifying context threaded onto exported spans (Phoenix shows the same
454
+ * source/run identifiers as the local trace-source registry, so local artifact
455
+ * lookup stays possible). Resolved with the same source-id/label resolvers the
456
+ * trace source uses.
457
+ */
458
+ async observabilityContext() {
459
+ const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
460
+ const [sourceId, sourceLabel] = await Promise.all([
461
+ resolveAppTraceSourceId(input, this.traceDefaults),
462
+ resolveAppTraceSourceLabel(input, this.traceDefaults),
463
+ ]);
464
+ return { sourceId, sourceLabel, configPath: this.configPath };
465
+ }
466
+ memoryRecallRuntimeOptions(coreConfig) {
467
+ const settings = resolveMemoryRecallSettings(coreConfig);
468
+ if (settings === undefined) {
469
+ return undefined;
470
+ }
471
+ this.logger?.info?.("Read-only memory_recall tool enabled.", {
472
+ provider: settings.embeddings?.provider ?? "fts-only",
473
+ });
474
+ return createMemoryRecallRuntimeExtension(settings, this.cwd);
475
+ }
476
+ async adapterSendToolsRuntimeOptions(coreConfig) {
477
+ const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
478
+ const settings = await resolveAdapterSendToolsSettings(input, {
479
+ allowedTools: coreConfig.tools.allowedTools,
480
+ disallowedTools: coreConfig.tools.disallowedTools,
481
+ logger: this.logger,
482
+ });
483
+ if (settings === undefined) {
484
+ return undefined;
485
+ }
486
+ const toolNames = adapterSendToolNames(settings);
487
+ this.logger?.info?.("Adapter send tools enabled.", { tools: toolNames });
488
+ return createAdapterSendToolsRuntimeExtension(this.configPath, this.cwd, toolNames);
489
+ }
490
+ /** Build the configured memory store once and share it across responders + the ritual scheduler. */
491
+ memoryStore(coreConfig, runtime) {
492
+ if (!this.sharedMemoryBuilt) {
493
+ const appLogger = this.logger;
494
+ const logger = appLogger?.warn !== undefined
495
+ ? { warn: (message) => { appLogger.warn?.(message); } }
496
+ : undefined;
497
+ this.sharedMemory = createConfiguredMemory(coreConfig, { ...(logger === undefined ? {} : { logger }), runtime });
498
+ this.sharedMemoryBuilt = true;
499
+ }
500
+ return this.sharedMemory;
501
+ }
502
+ /** Close + clear the shared memory store (on config reload or stop) so the next build is fresh. */
503
+ async resetSharedMemory() {
504
+ const mem = this.sharedMemory;
505
+ this.sharedMemory = undefined;
506
+ this.sharedMemoryBuilt = false;
507
+ if (mem?.flush !== undefined) {
508
+ await Promise.resolve(mem.flush()).catch(() => undefined);
509
+ }
510
+ if (mem?.close !== undefined) {
511
+ await Promise.resolve(mem.close()).catch(() => undefined);
512
+ }
513
+ }
514
+ /** @internal Test-only seam: seed the shared memory store without going through config. */
515
+ __setSharedMemoryForTest(store) {
516
+ this.sharedMemory = store;
517
+ this.sharedMemoryBuilt = true;
320
518
  }
321
519
  setStatus(id, status) {
322
520
  this.statuses.set(id, status);
@@ -370,9 +568,6 @@ class MonoAgentAppController {
370
568
  }
371
569
  activeTransports() {
372
570
  const transports = [];
373
- if (this.consoleServer !== undefined) {
374
- transports.push("operator-console");
375
- }
376
571
  for (const driver of this.drivers) {
377
572
  if (this.statuses.get(driver.id)?.kind === "running") {
378
573
  transports.push(driver.id);
@@ -393,29 +588,96 @@ class MonoAgentAppController {
393
588
  }
394
589
  return {
395
590
  reason,
396
- ...(this.operatorConsole === undefined
397
- ? {}
398
- : {
399
- operatorConsole: {
400
- // Only the base loopback URL is persisted never the per-boot
401
- // access token. A detached `status`/`start` reader surfaces this
402
- // URL and points at the logs for the tokenized link.
403
- url: this.operatorConsole.url,
404
- configPath: this.operatorConsole.configPath,
591
+ ...(this.exporterStatusValue.kind === "configured"
592
+ ? {
593
+ observability: {
594
+ // Persist only the endpoint + warning/error strings (never headers
595
+ // or secrets) so the detached `status` reader can surface exporter
596
+ // state. JSONL artifacts always remain local.
597
+ endpoint: this.exporterStatusValue.endpoint,
598
+ includeSensitiveData: this.exporterStatusValue.includeSensitiveData,
599
+ jsonlArtifactsLocal: true,
600
+ ...(this.exporterStatusValue.lastWarning === undefined
601
+ ? {}
602
+ : { lastWarning: this.exporterStatusValue.lastWarning }),
603
+ ...(this.exporterStatusValue.lastError === undefined
604
+ ? {}
605
+ : { lastError: this.exporterStatusValue.lastError }),
405
606
  },
406
- }),
607
+ }
608
+ : {}),
407
609
  channels,
408
610
  };
409
611
  }
410
612
  }
411
- function logOperatorConsoleEvent(logger, event) {
412
- if (event.kind === "validation_failed" || event.kind === "unauthorized") {
413
- logger?.warn?.("Operator Console event.", { event });
613
+ function reasonOf(error) {
614
+ return error instanceof Error ? error.message : String(error);
615
+ }
616
+ /**
617
+ * Compose several per-request runtime-options extensions into one. Each extension is invoked per
618
+ * request; mergeable runtime option maps/lists are unioned and their cleanups are chained. Returns
619
+ * `undefined` when no extension is active, so the host omits `runtimeOptionsForRequest` entirely.
620
+ */
621
+ function composeRuntimeOptionExtensions(extensions) {
622
+ const active = extensions.filter((extension) => extension !== undefined);
623
+ if (active.length === 0) {
624
+ return undefined;
625
+ }
626
+ if (active.length === 1) {
627
+ return active[0];
628
+ }
629
+ return async (input) => {
630
+ const results = await Promise.all(active.map((extension) => extension(input)));
631
+ const runtimeOptions = {};
632
+ for (const result of results) {
633
+ mergeRuntimeOptions(runtimeOptions, result.runtimeOptions);
634
+ }
635
+ return {
636
+ runtimeOptions,
637
+ cleanup: async () => {
638
+ // Chain every cleanup; run them all even if one rejects so no extension leaks resources.
639
+ await Promise.all(results.map(async (result) => result.cleanup?.()));
640
+ },
641
+ };
642
+ };
643
+ }
644
+ function mergeRuntimeOptions(target, next) {
645
+ if (next === undefined) {
414
646
  return;
415
647
  }
416
- logger?.debug?.("Operator Console event.", { event });
648
+ for (const [key, value] of Object.entries(next)) {
649
+ if (value === undefined) {
650
+ continue;
651
+ }
652
+ if (key === "allowedTools" || key === "disallowedTools") {
653
+ target[key] = mergeStringLists(target[key], value);
654
+ continue;
655
+ }
656
+ if (key === "mcpServers") {
657
+ target[key] = {
658
+ ...(isRecord(target[key]) ? target[key] : {}),
659
+ ...(isRecord(value) ? value : {}),
660
+ };
661
+ continue;
662
+ }
663
+ target[key] = value;
664
+ }
417
665
  }
418
- function reasonOf(error) {
419
- return error instanceof Error ? error.message : String(error);
666
+ function mergeStringLists(current, next) {
667
+ const out = [];
668
+ for (const list of [current, next]) {
669
+ if (!Array.isArray(list)) {
670
+ continue;
671
+ }
672
+ for (const item of list) {
673
+ if (typeof item === "string" && !out.includes(item)) {
674
+ out.push(item);
675
+ }
676
+ }
677
+ }
678
+ return out;
679
+ }
680
+ function isRecord(value) {
681
+ return typeof value === "object" && value !== null && !Array.isArray(value);
420
682
  }
421
683
  //# sourceMappingURL=app.js.map