@mono-agent/agent-app 0.6.1 → 0.8.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 (211) hide show
  1. package/README.md +210 -17
  2. package/dist/adapter-send-proxy.d.ts +18 -0
  3. package/dist/adapter-send-proxy.d.ts.map +1 -0
  4. package/dist/adapter-send-proxy.js +229 -0
  5. package/dist/adapter-send-proxy.js.map +1 -0
  6. package/dist/adapter-send-tools-main.js +62 -4
  7. package/dist/adapter-send-tools-main.js.map +1 -1
  8. package/dist/adapter-send-tools.d.ts +41 -8
  9. package/dist/adapter-send-tools.d.ts.map +1 -1
  10. package/dist/adapter-send-tools.js +563 -108
  11. package/dist/adapter-send-tools.js.map +1 -1
  12. package/dist/app-config.d.ts.map +1 -1
  13. package/dist/app-config.js +8 -0
  14. package/dist/app-config.js.map +1 -1
  15. package/dist/app.d.ts +2 -0
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/app.js +532 -134
  18. package/dist/app.js.map +1 -1
  19. package/dist/broadcast-recorder.d.ts.map +1 -1
  20. package/dist/broadcast-recorder.js +24 -2
  21. package/dist/broadcast-recorder.js.map +1 -1
  22. package/dist/channels.d.ts.map +1 -1
  23. package/dist/channels.js +18 -7
  24. package/dist/channels.js.map +1 -1
  25. package/dist/cli.d.ts +77 -4
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +2106 -99
  28. package/dist/cli.js.map +1 -1
  29. package/dist/config-reference.d.ts.map +1 -1
  30. package/dist/config-reference.js +195 -6
  31. package/dist/config-reference.js.map +1 -1
  32. package/dist/configuration-proposal-main.d.ts +3 -0
  33. package/dist/configuration-proposal-main.d.ts.map +1 -0
  34. package/dist/configuration-proposal-main.js +13 -0
  35. package/dist/configuration-proposal-main.js.map +1 -0
  36. package/dist/configuration-proposal-tool.d.ts +26 -0
  37. package/dist/configuration-proposal-tool.d.ts.map +1 -0
  38. package/dist/configuration-proposal-tool.js +139 -0
  39. package/dist/configuration-proposal-tool.js.map +1 -0
  40. package/dist/configured-agent.d.ts +12 -0
  41. package/dist/configured-agent.d.ts.map +1 -1
  42. package/dist/configured-agent.js +150 -18
  43. package/dist/configured-agent.js.map +1 -1
  44. package/dist/consumer-contract.d.ts +3 -0
  45. package/dist/consumer-contract.d.ts.map +1 -1
  46. package/dist/consumer-contract.js +44 -1
  47. package/dist/consumer-contract.js.map +1 -1
  48. package/dist/doctor.d.ts +27 -5
  49. package/dist/doctor.d.ts.map +1 -1
  50. package/dist/doctor.js +1198 -96
  51. package/dist/doctor.js.map +1 -1
  52. package/dist/first-run-managed-memory.d.ts +39 -0
  53. package/dist/first-run-managed-memory.d.ts.map +1 -0
  54. package/dist/first-run-managed-memory.js +521 -0
  55. package/dist/first-run-managed-memory.js.map +1 -0
  56. package/dist/first-run-readiness.d.ts +111 -0
  57. package/dist/first-run-readiness.d.ts.map +1 -0
  58. package/dist/first-run-readiness.js +757 -0
  59. package/dist/first-run-readiness.js.map +1 -0
  60. package/dist/index.d.ts +8 -3
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +4 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/init.d.ts +88 -3
  65. package/dist/init.d.ts.map +1 -1
  66. package/dist/init.js +1212 -23
  67. package/dist/init.js.map +1 -1
  68. package/dist/interaction-bridge.d.ts +32 -2
  69. package/dist/interaction-bridge.d.ts.map +1 -1
  70. package/dist/interaction-bridge.js +309 -28
  71. package/dist/interaction-bridge.js.map +1 -1
  72. package/dist/local-configuration.d.ts +56 -0
  73. package/dist/local-configuration.d.ts.map +1 -0
  74. package/dist/local-configuration.js +914 -0
  75. package/dist/local-configuration.js.map +1 -0
  76. package/dist/memory-command.d.ts +4 -0
  77. package/dist/memory-command.d.ts.map +1 -1
  78. package/dist/memory-command.js +751 -27
  79. package/dist/memory-command.js.map +1 -1
  80. package/dist/memory-recall.d.ts +38 -10
  81. package/dist/memory-recall.d.ts.map +1 -1
  82. package/dist/memory-recall.js +82 -25
  83. package/dist/memory-recall.js.map +1 -1
  84. package/dist/memory-retrieval.d.ts +66 -0
  85. package/dist/memory-retrieval.d.ts.map +1 -0
  86. package/dist/memory-retrieval.js +332 -0
  87. package/dist/memory-retrieval.js.map +1 -0
  88. package/dist/modules/base.d.ts +6 -5
  89. package/dist/modules/base.d.ts.map +1 -1
  90. package/dist/modules/base.js +11 -7
  91. package/dist/modules/base.js.map +1 -1
  92. package/dist/modules/catalog.d.ts.map +1 -1
  93. package/dist/modules/catalog.js +54 -20
  94. package/dist/modules/catalog.js.map +1 -1
  95. package/dist/modules/index.d.ts +2 -2
  96. package/dist/modules/index.d.ts.map +1 -1
  97. package/dist/modules/index.js +1 -1
  98. package/dist/modules/index.js.map +1 -1
  99. package/dist/modules/known-tools.d.ts +3 -0
  100. package/dist/modules/known-tools.d.ts.map +1 -1
  101. package/dist/modules/known-tools.js +11 -1
  102. package/dist/modules/known-tools.js.map +1 -1
  103. package/dist/modules/types.d.ts +20 -6
  104. package/dist/modules/types.d.ts.map +1 -1
  105. package/dist/modules/types.js.map +1 -1
  106. package/dist/package-version.d.ts +3 -0
  107. package/dist/package-version.d.ts.map +1 -0
  108. package/dist/package-version.js +14 -0
  109. package/dist/package-version.js.map +1 -0
  110. package/dist/pi-auth-store-inspection.d.ts +21 -0
  111. package/dist/pi-auth-store-inspection.d.ts.map +1 -0
  112. package/dist/pi-auth-store-inspection.js +120 -0
  113. package/dist/pi-auth-store-inspection.js.map +1 -0
  114. package/dist/pi-oauth-login-main.d.ts +3 -0
  115. package/dist/pi-oauth-login-main.d.ts.map +1 -0
  116. package/dist/pi-oauth-login-main.js +14 -0
  117. package/dist/pi-oauth-login-main.js.map +1 -0
  118. package/dist/pi-oauth-login.d.ts +22 -0
  119. package/dist/pi-oauth-login.d.ts.map +1 -0
  120. package/dist/pi-oauth-login.js +79 -0
  121. package/dist/pi-oauth-login.js.map +1 -0
  122. package/dist/project-skills.d.ts +40 -0
  123. package/dist/project-skills.d.ts.map +1 -0
  124. package/dist/project-skills.js +573 -0
  125. package/dist/project-skills.js.map +1 -0
  126. package/dist/provider-setup.d.ts +136 -5
  127. package/dist/provider-setup.d.ts.map +1 -1
  128. package/dist/provider-setup.js +1552 -33
  129. package/dist/provider-setup.js.map +1 -1
  130. package/dist/readiness-probe-worker.d.ts +12 -0
  131. package/dist/readiness-probe-worker.d.ts.map +1 -0
  132. package/dist/readiness-probe-worker.js +276 -0
  133. package/dist/readiness-probe-worker.js.map +1 -0
  134. package/dist/readiness-probe.d.ts +100 -0
  135. package/dist/readiness-probe.d.ts.map +1 -0
  136. package/dist/readiness-probe.js +780 -0
  137. package/dist/readiness-probe.js.map +1 -0
  138. package/dist/request-model-override.d.ts +43 -1
  139. package/dist/request-model-override.d.ts.map +1 -1
  140. package/dist/request-model-override.js +178 -19
  141. package/dist/request-model-override.js.map +1 -1
  142. package/dist/run-history.d.ts +32 -0
  143. package/dist/run-history.d.ts.map +1 -0
  144. package/dist/run-history.js +859 -0
  145. package/dist/run-history.js.map +1 -0
  146. package/dist/runs-health.d.ts.map +1 -1
  147. package/dist/runs-health.js +12 -4
  148. package/dist/runs-health.js.map +1 -1
  149. package/dist/runtime-option-extensions.d.ts +14 -0
  150. package/dist/runtime-option-extensions.d.ts.map +1 -0
  151. package/dist/runtime-option-extensions.js +98 -0
  152. package/dist/runtime-option-extensions.js.map +1 -0
  153. package/dist/runtime-routes.d.ts +9 -0
  154. package/dist/runtime-routes.d.ts.map +1 -0
  155. package/dist/runtime-routes.js +13 -0
  156. package/dist/runtime-routes.js.map +1 -0
  157. package/dist/sandbox-manager.d.ts +69 -0
  158. package/dist/sandbox-manager.d.ts.map +1 -0
  159. package/dist/sandbox-manager.js +856 -0
  160. package/dist/sandbox-manager.js.map +1 -0
  161. package/dist/supermemory-plugin.d.ts +54 -0
  162. package/dist/supermemory-plugin.d.ts.map +1 -0
  163. package/dist/supermemory-plugin.js +136 -0
  164. package/dist/supermemory-plugin.js.map +1 -0
  165. package/dist/tui-command.d.ts +15 -0
  166. package/dist/tui-command.d.ts.map +1 -1
  167. package/dist/tui-command.js +46 -6
  168. package/dist/tui-command.js.map +1 -1
  169. package/dist/web-command.d.ts +8 -0
  170. package/dist/web-command.d.ts.map +1 -1
  171. package/dist/web-command.js +174 -17
  172. package/dist/web-command.js.map +1 -1
  173. package/dist/wizard/answers.d.ts +31 -4
  174. package/dist/wizard/answers.d.ts.map +1 -1
  175. package/dist/wizard/answers.js +145 -16
  176. package/dist/wizard/answers.js.map +1 -1
  177. package/dist/wizard/from-flags.d.ts +7 -0
  178. package/dist/wizard/from-flags.d.ts.map +1 -1
  179. package/dist/wizard/from-flags.js +36 -2
  180. package/dist/wizard/from-flags.js.map +1 -1
  181. package/dist/wizard/index.d.ts +4 -4
  182. package/dist/wizard/index.d.ts.map +1 -1
  183. package/dist/wizard/index.js +2 -2
  184. package/dist/wizard/index.js.map +1 -1
  185. package/dist/wizard/model-discovery.d.ts +38 -3
  186. package/dist/wizard/model-discovery.d.ts.map +1 -1
  187. package/dist/wizard/model-discovery.js +927 -102
  188. package/dist/wizard/model-discovery.js.map +1 -1
  189. package/dist/wizard/presets.d.ts +1 -1
  190. package/dist/wizard/presets.d.ts.map +1 -1
  191. package/dist/wizard/presets.js +7 -12
  192. package/dist/wizard/presets.js.map +1 -1
  193. package/dist/wizard/prompts.d.ts +66 -11
  194. package/dist/wizard/prompts.d.ts.map +1 -1
  195. package/dist/wizard/prompts.js +190 -20
  196. package/dist/wizard/prompts.js.map +1 -1
  197. package/dist/wizard/run.d.ts +60 -3
  198. package/dist/wizard/run.d.ts.map +1 -1
  199. package/dist/wizard/run.js +1161 -145
  200. package/dist/wizard/run.js.map +1 -1
  201. package/package.json +21 -16
  202. package/resources/srt/package-lock.json +66 -0
  203. package/resources/srt/package.json +9 -0
  204. package/schema/mono-agent.config.schema.json +381 -17
  205. package/skills/mono-agent-composer/SKILL.md +4 -4
  206. package/skills/mono-agent-composer/references/config-blueprint.md +5 -4
  207. package/skills/mono-agent-composer/references/discovery-questions.md +8 -8
  208. package/skills/mono-agent-composer/references/feature-coverage.md +8 -6
  209. package/skills/mono-agent-composer/references/package-map.md +1 -1
  210. package/skills/mono-agent-composer/references/playbooks.md +8 -7
  211. package/skills/mono-agent-composer/references/validation.md +1 -1
package/dist/app.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { loadToolPolicyFromJsonFileSync } from "@mono-agent/agent-harness";
2
3
  import { createLiveEventBus } from "@mono-agent/agent-contracts";
3
4
  import { pruneTraceSources, reconcileStaleRunArtifacts, registerTraceSource } from "@mono-agent/observability";
4
5
  import { modelReferenceKey } from "@mono-agent/runtime-adapter";
@@ -11,10 +12,14 @@ import { adapterSendToolNames, createAdapterSendToolsRuntimeExtension, isAdapter
11
12
  import { loadInteractionSettings, startInteractionBridge } from "./interaction-bridge.js";
12
13
  import { startMemoryRituals } from "./memory-rituals.js";
13
14
  import { startArtifactRetentionScheduler } from "./artifact-retention.js";
14
- import { createMemoryRecallRuntimeExtension, resolveMemoryRecallSettings, } from "./memory-recall.js";
15
- import { createRequestModelOverrideRuntimeExtension } from "./request-model-override.js";
15
+ import { resolveMemoryRecallSettings, } from "./memory-recall.js";
16
+ import { isSharedRecallStore, MemoryRetrievalService, } from "./memory-retrieval.js";
17
+ import { composeRuntimeOptionExtensions, } from "./runtime-option-extensions.js";
18
+ import { createRunHistoryRuntimeExtension, isRunHistoryToolAllowed, } from "./run-history.js";
19
+ import { createRequestModelOverrideRuntimeExtension, requestModelOverrideTargetsDirectOpenCode, } from "./request-model-override.js";
16
20
  import { resolveNotifyDestinations } from "./notify-destinations.js";
17
21
  import { resolvePostedMessageIndexPath } from "./posted-message-index.js";
22
+ import { configuredRuntimeFallbackModels, configuredRuntimeModels, hasConfiguredRuntimeFallbacks, } from "./runtime-routes.js";
18
23
  /**
19
24
  * Starts a config-first mono-agent host in `cwd`: traceability first, then every
20
25
  * configured channel in parallel. Channels with incomplete config report
@@ -41,7 +46,7 @@ export async function startMonoAgentApp(options = {}) {
41
46
  await controller.startExporters("startup");
42
47
  await Promise.all(drivers.map((driver) => controller?.startChannelIfConfigured(driver.id, "startup")));
43
48
  await controller.startMemoryRitualsIfConfigured("startup");
44
- await controller.refreshTraceSource("startup-complete");
49
+ await controller.refreshMemoryHealthAfterLifecycle("startup-complete");
45
50
  return controller;
46
51
  }
47
52
  const DEFAULT_SANDBOX_STATUS = sandboxStatusFromState({
@@ -54,6 +59,8 @@ const DEFAULT_SANDBOX_STATUS = sandboxStatusFromState({
54
59
  fallbackActive: false,
55
60
  unsafeAllowHostProcess: false,
56
61
  });
62
+ /** Strict memory audits scan durable state; never run them at sub-second trace-heartbeat cadence. */
63
+ const MIN_MEMORY_HEALTH_REFRESH_INTERVAL_MS = 30_000;
57
64
  class MonoAgentAppController {
58
65
  configPath;
59
66
  cwd;
@@ -80,12 +87,32 @@ class MonoAgentAppController {
80
87
  reason: "No observability exporter configured.",
81
88
  };
82
89
  sandboxStatusValue = DEFAULT_SANDBOX_STATUS;
90
+ memoryHealthValue = {
91
+ backend: "none",
92
+ status: "unknown",
93
+ checkedAt: new Date().toISOString(),
94
+ };
95
+ memoryHealthRefreshInFlight;
96
+ memoryHealthRefreshTimer;
97
+ memoryHealthRefreshLoopActive = false;
98
+ memoryHealthRefreshIntervalMs = MIN_MEMORY_HEALTH_REFRESH_INTERVAL_MS;
99
+ /** Monotonic completion time for the current generation's cached full audit. */
100
+ memoryHealthLastCompletedAtMs;
101
+ /** One bounded forced refresh reserved by a due timer tick. */
102
+ memoryHealthRefreshDue = false;
103
+ memoryHealthGeneration = 0;
83
104
  selectedSkillsValue;
84
105
  sessionMetadataValue;
85
106
  /** The exporter the responder threads into agent-host (first configured exporter). */
86
107
  resolvedExporter;
87
108
  traceSource;
88
- traceRefreshTail = Promise.resolve();
109
+ /**
110
+ * Whole-publication single flight. A slow health probe must not let periodic
111
+ * heartbeat ticks build an unbounded promise tail behind it.
112
+ */
113
+ traceRefreshInFlight;
114
+ traceRefreshDirty = false;
115
+ traceRefreshLatestReason;
89
116
  // Best-effort mirror of `traceSource` into the machine-wide global registry,
90
117
  // present only when `shouldMirrorTraceSourceGlobally` gates it on (see
91
118
  // `startTraceability`). Kept in lockstep with `traceSource` on every
@@ -97,15 +124,16 @@ class MonoAgentAppController {
97
124
  // One shared memory store across all channel responders + the ritual scheduler, so there is a single
98
125
  // memory.db handle (not one per channel plus one for rituals). Rebuilt on config reload, closed on stop.
99
126
  sharedMemory = undefined;
127
+ sharedMemoryRetrieval;
100
128
  sharedMemoryBuilt = false;
101
129
  sharedMemoryBuild;
102
130
  configApplyTail = Promise.resolve();
103
131
  stopped = false;
104
- // Interaction bridge (AskUser + tool progress): lazily started once, shared
105
- // by every channel; the exported env keys are tracked for cleanup on stop.
132
+ // Interaction bridge (AskUser + tool progress): lazily started once and shared
133
+ // by every channel. Its master bearer stays app-owned and is never exported
134
+ // through the generic host/process environment.
106
135
  interactionBridge;
107
136
  interactionBridgeStart;
108
- interactionBridgeEnvKeys = [];
109
137
  // Shared in-process run-event bus: every run's recorder publishes to it (via the
110
138
  // broadcast recorder threaded as `runEventSink`), and the `live` channel relays
111
139
  // it over SSE. One instance for the app's lifetime — cheap, bounded ring buffer,
@@ -137,6 +165,9 @@ class MonoAgentAppController {
137
165
  get sandboxStatus() {
138
166
  return this.sandboxStatusValue;
139
167
  }
168
+ get memoryHealth() {
169
+ return this.memoryHealthValue;
170
+ }
140
171
  get selectedSkills() {
141
172
  return this.selectedSkillsValue;
142
173
  }
@@ -159,9 +190,17 @@ class MonoAgentAppController {
159
190
  transports: [],
160
191
  };
161
192
  }
193
+ // Freeze periodic memory work before any channel/store teardown. A probe
194
+ // that already entered is generation-fenced and is deliberately not
195
+ // awaited, so config reload cannot hang behind native/filesystem work.
196
+ this.invalidateMemoryHealthRefresh();
162
197
  for (const driver of this.drivers) {
163
198
  await this.stopChannel(driver.id, `${reason}:reload`);
164
199
  }
200
+ // Tool policy/runtime-family changes must re-evaluate implicit AskUser.
201
+ // Clearing the cached promise also prevents stale bridge env from a Pi
202
+ // config leaking into a reloaded direct-OpenCode responder.
203
+ await this.stopInteractionBridge();
165
204
  this.stopMemoryRituals();
166
205
  this.stopArtifactRetentionScheduler();
167
206
  await this.resetSharedMemory();
@@ -171,7 +210,7 @@ class MonoAgentAppController {
171
210
  await this.startExporters(reason);
172
211
  await Promise.all(this.drivers.map((driver) => this.startChannelIfConfigured(driver.id, reason)));
173
212
  await this.startMemoryRitualsIfConfigured(reason);
174
- await this.refreshTraceSource(`${reason}:complete`);
213
+ await this.refreshMemoryHealthAfterLifecycle(`${reason}:complete`);
175
214
  return this.applyResult();
176
215
  };
177
216
  const next = this.configApplyTail.then(run, run);
@@ -207,6 +246,7 @@ class MonoAgentAppController {
207
246
  try {
208
247
  const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
209
248
  await this.refreshSelectedSkillsSnapshot(reason);
249
+ await this.refreshMemoryHealthSnapshot(reason);
210
250
  const [registryDir, artifactDir, sourceId, label, heartbeatMs, globalDiscovery] = await Promise.all([
211
251
  resolveAppTraceRegistryDir(input),
212
252
  resolveAppArtifactDir(input),
@@ -225,6 +265,7 @@ class MonoAgentAppController {
225
265
  transports: this.activeTransports(),
226
266
  configPath: this.configPath,
227
267
  metadata: this.traceMetadata(reason),
268
+ memoryHealth: this.memoryHealthValue,
228
269
  heartbeatMs,
229
270
  };
230
271
  this.traceSource = await registerTraceSource(registerOptions);
@@ -250,8 +291,10 @@ class MonoAgentAppController {
250
291
  else {
251
292
  this.globalTraceSource = undefined;
252
293
  }
294
+ this.startMemoryHealthRefreshLoop(MIN_MEMORY_HEALTH_REFRESH_INTERVAL_MS);
253
295
  }
254
296
  catch (error) {
297
+ this.clearMemoryHealthRefreshTimer();
255
298
  const failure = reasonOf(error);
256
299
  this.traceabilityStatusValue = { kind: "failed", reason: failure };
257
300
  this.logger?.error?.("Traceability source registration failed.", { reason: failure });
@@ -379,45 +422,271 @@ class MonoAgentAppController {
379
422
  // (which reads the manifest, not this live object) can surface it too.
380
423
  void this.refreshTraceSource("exporter-warning").catch(() => undefined);
381
424
  }
382
- async refreshTraceSource(reason) {
383
- const run = async () => {
384
- if (this.traceSource === undefined) {
385
- return;
386
- }
387
- const patch = { transports: this.activeTransports(), metadata: this.traceMetadata(reason) };
388
- try {
389
- await this.traceSource.update(patch);
390
- }
391
- catch (error) {
392
- this.logger?.warn?.("Traceability source update failed.", { reason: reasonOf(error) });
393
- }
394
- if (this.globalTraceSource !== undefined) {
425
+ refreshTraceSource(reason) {
426
+ const existing = this.traceRefreshInFlight;
427
+ if (existing !== undefined) {
428
+ // Keep one bounded replay slot. Concurrent explicit callers share the
429
+ // exact owner promise; the owner publishes the latest state/reason once
430
+ // more after its current write. Requests arriving during that replay set
431
+ // the same bit again, without growing a callback queue.
432
+ this.traceRefreshDirty = true;
433
+ this.traceRefreshLatestReason = reason;
434
+ return existing;
435
+ }
436
+ const traceSource = this.traceSource;
437
+ const globalTraceSource = this.globalTraceSource;
438
+ if (traceSource === undefined) {
439
+ return Promise.resolve();
440
+ }
441
+ const generation = this.memoryHealthGeneration;
442
+ let pending;
443
+ pending = (async () => {
444
+ let publishReason = reason;
445
+ for (;;) {
446
+ this.traceRefreshDirty = false;
447
+ this.traceRefreshLatestReason = undefined;
448
+ const health = await this.refreshMemoryHealthSnapshot(publishReason);
449
+ if (generation !== this.memoryHealthGeneration || this.traceSource !== traceSource) {
450
+ return;
451
+ }
452
+ const patch = {
453
+ transports: this.activeTransports(),
454
+ metadata: this.traceMetadata(publishReason),
455
+ memoryHealth: health,
456
+ };
395
457
  try {
396
- await this.globalTraceSource.update(patch);
458
+ await traceSource.update(patch);
397
459
  }
398
460
  catch (error) {
399
- this.logger?.warn?.("Global trace-source mirror update failed.", { reason: reasonOf(error) });
461
+ this.logger?.warn?.("Traceability source update failed.", { reason: reasonOf(error) });
462
+ }
463
+ if (globalTraceSource !== undefined && generation === this.memoryHealthGeneration
464
+ && this.globalTraceSource === globalTraceSource) {
465
+ try {
466
+ await globalTraceSource.update(patch);
467
+ }
468
+ catch (error) {
469
+ this.logger?.warn?.("Global trace-source mirror update failed.", { reason: reasonOf(error) });
470
+ }
400
471
  }
472
+ if (!this.traceRefreshDirty || generation !== this.memoryHealthGeneration
473
+ || this.traceSource !== traceSource) {
474
+ return;
475
+ }
476
+ publishReason = this.traceRefreshLatestReason ?? publishReason;
401
477
  }
402
- };
403
- const next = this.traceRefreshTail.then(run, run);
404
- this.traceRefreshTail = next.then(() => undefined, () => undefined);
405
- await next;
478
+ })().catch(() => {
479
+ // Health computation and trace handles already sanitize/log their own
480
+ // failures. Keep this final boundary closed so background refreshes never
481
+ // become unhandled rejections or leak raw provider/native errors.
482
+ }).finally(() => {
483
+ if (this.traceRefreshInFlight === pending) {
484
+ this.traceRefreshInFlight = undefined;
485
+ const replayDue = this.memoryHealthRefreshDue
486
+ && !this.stopped
487
+ && generation === this.memoryHealthGeneration
488
+ && this.traceSource === traceSource;
489
+ this.traceRefreshDirty = false;
490
+ this.traceRefreshLatestReason = undefined;
491
+ // A timer can fire after the loop's final dirty check but before this
492
+ // owner settles. Preserve exactly one due replay across that seam.
493
+ if (replayDue) {
494
+ void this.refreshTraceSource("memory-health-periodic").catch(() => undefined);
495
+ }
496
+ }
497
+ });
498
+ this.traceRefreshInFlight = pending;
499
+ return pending;
406
500
  }
407
501
  /**
408
- * Start the interaction bridge once, when the AskUser tool is allowed by the
409
- * tool policy or the operator configured the `interaction` block. Exports the
410
- * bridge env into the app env AND process env so settings resolution and
411
- * spawned stdio tool children (which inherit process.env) can reach it.
502
+ * Refresh the content-free memory snapshot once for all concurrent trace
503
+ * publishers. The cached value is the only memory state read by synchronous
504
+ * manifest composition; provider/native errors collapse to a closed unknown
505
+ * shape and never escape into registry metadata.
506
+ */
507
+ refreshMemoryHealthSnapshot(reason, lifecycleForce = false) {
508
+ if (this.stopped) {
509
+ return Promise.resolve(this.memoryHealthValue);
510
+ }
511
+ if (this.memoryHealthRefreshInFlight !== undefined) {
512
+ return this.memoryHealthRefreshInFlight;
513
+ }
514
+ const forced = lifecycleForce || this.memoryHealthRefreshDue;
515
+ if (!forced && this.memoryHealthLastCompletedAtMs !== undefined
516
+ && performance.now() - this.memoryHealthLastCompletedAtMs < MIN_MEMORY_HEALTH_REFRESH_INTERVAL_MS) {
517
+ return Promise.resolve(this.memoryHealthValue);
518
+ }
519
+ const generation = this.memoryHealthGeneration;
520
+ let pending;
521
+ pending = this.computeMemoryHealth().then((health) => {
522
+ if (!this.stopped && generation === this.memoryHealthGeneration) {
523
+ this.memoryHealthValue = health;
524
+ this.recordMemoryHealthCompletion(generation);
525
+ return health;
526
+ }
527
+ return this.memoryHealthValue;
528
+ }).catch(() => {
529
+ const health = this.memoryHealthValue.backend === "bujo"
530
+ ? unknownBujoMemoryHealth(this.memoryHealthValue.mode)
531
+ : this.memoryHealthValue.backend === "supermemory"
532
+ ? {
533
+ backend: "supermemory",
534
+ status: "unknown",
535
+ checkedAt: new Date().toISOString(),
536
+ }
537
+ : unknownNoMemoryHealth();
538
+ if (!this.stopped && generation === this.memoryHealthGeneration) {
539
+ this.memoryHealthValue = health;
540
+ this.recordMemoryHealthCompletion(generation);
541
+ }
542
+ this.logger?.warn?.("Memory health refresh failed; publishing sanitized unknown health.", { reason });
543
+ return health;
544
+ }).finally(() => {
545
+ if (this.memoryHealthRefreshInFlight === pending) {
546
+ this.memoryHealthRefreshInFlight = undefined;
547
+ }
548
+ });
549
+ this.memoryHealthRefreshInFlight = pending;
550
+ return pending;
551
+ }
552
+ async computeMemoryHealth() {
553
+ let config;
554
+ try {
555
+ config = await loadAppCoreConfig({ env: this.env, cwd: this.cwd, configPath: this.configPath });
556
+ }
557
+ catch {
558
+ return unknownNoMemoryHealth();
559
+ }
560
+ const memory = config.memory;
561
+ if (memory === undefined) {
562
+ return {
563
+ backend: "none",
564
+ status: "not_configured",
565
+ checkedAt: new Date().toISOString(),
566
+ };
567
+ }
568
+ if ((memory.backend ?? "bujo") === "supermemory") {
569
+ return {
570
+ backend: "supermemory",
571
+ status: "unknown",
572
+ checkedAt: new Date().toISOString(),
573
+ };
574
+ }
575
+ try {
576
+ const { auditBujoMemoryHealth } = await import("@mono-agent/memory/bujo");
577
+ return traceMemoryHealthFromBujo(auditBujoMemoryHealth({
578
+ root: memory.path,
579
+ mode: memory.mode,
580
+ ...(memory.embeddings === undefined
581
+ ? {}
582
+ : {
583
+ configuredEmbeddingModel: `${memory.embeddings.provider}:${memory.embeddings.model}`,
584
+ configuredDimension: memory.embeddings.dim ?? 768,
585
+ }),
586
+ }));
587
+ }
588
+ catch {
589
+ return unknownBujoMemoryHealth(memory.mode);
590
+ }
591
+ }
592
+ startMemoryHealthRefreshLoop(intervalMs) {
593
+ this.memoryHealthRefreshLoopActive = true;
594
+ this.memoryHealthRefreshIntervalMs = intervalMs;
595
+ this.scheduleMemoryHealthRefresh();
596
+ }
597
+ scheduleMemoryHealthRefresh(delayOverrideMs) {
598
+ this.clearMemoryHealthRefreshTimer();
599
+ if (!this.memoryHealthRefreshLoopActive || this.stopped || this.traceSource === undefined)
600
+ return;
601
+ const elapsed = this.memoryHealthLastCompletedAtMs === undefined
602
+ ? this.memoryHealthRefreshIntervalMs
603
+ : Math.max(0, performance.now() - this.memoryHealthLastCompletedAtMs);
604
+ const delay = delayOverrideMs ?? Math.max(0, this.memoryHealthRefreshIntervalMs - elapsed);
605
+ this.memoryHealthRefreshTimer = setTimeout(() => {
606
+ this.memoryHealthRefreshTimer = undefined;
607
+ this.refreshMemoryHealthOnTimer();
608
+ }, delay);
609
+ this.memoryHealthRefreshTimer.unref?.();
610
+ }
611
+ recordMemoryHealthCompletion(generation) {
612
+ if (this.stopped || generation !== this.memoryHealthGeneration)
613
+ return;
614
+ this.memoryHealthLastCompletedAtMs = performance.now();
615
+ // Any full audit that was already running when the timer became due
616
+ // satisfies that due tick; never queue an immediate second audit.
617
+ this.memoryHealthRefreshDue = false;
618
+ this.scheduleMemoryHealthRefresh(this.memoryHealthRefreshIntervalMs);
619
+ }
620
+ refreshMemoryHealthOnTimer() {
621
+ if (this.stopped)
622
+ return;
623
+ if (this.memoryHealthLastCompletedAtMs !== undefined) {
624
+ const elapsed = performance.now() - this.memoryHealthLastCompletedAtMs;
625
+ if (elapsed < this.memoryHealthRefreshIntervalMs) {
626
+ this.scheduleMemoryHealthRefresh();
627
+ return;
628
+ }
629
+ }
630
+ // Set the due bit before joining the trace single-flight. If a publication
631
+ // is already active it consumes one bounded trailing replay rather than
632
+ // dropping this tick and waiting another full interval.
633
+ this.memoryHealthRefreshDue = true;
634
+ void this.refreshTraceSource("memory-health-periodic").catch(() => undefined);
635
+ }
636
+ /**
637
+ * Re-audit after channel responders and the shared memory store have started.
638
+ * The trace source is registered first, so its registration audit can only
639
+ * observe the previous/stopped runtime snapshot. This one lifecycle force is
640
+ * generation-fenced and uses the same health/trace single-flights as timer
641
+ * work; ordinary session and trace events continue to reuse the 30s cache.
642
+ */
643
+ async refreshMemoryHealthAfterLifecycle(reason) {
644
+ if (this.stopped)
645
+ return;
646
+ const generation = this.memoryHealthGeneration;
647
+ const joinedExistingAudit = this.memoryHealthRefreshInFlight !== undefined;
648
+ await this.refreshMemoryHealthSnapshot(reason, true);
649
+ if (joinedExistingAudit && !this.stopped && generation === this.memoryHealthGeneration) {
650
+ await this.refreshMemoryHealthSnapshot(reason, true);
651
+ }
652
+ if (this.stopped || generation !== this.memoryHealthGeneration)
653
+ return;
654
+ await this.refreshTraceSource(reason);
655
+ }
656
+ clearMemoryHealthRefreshTimer() {
657
+ if (this.memoryHealthRefreshTimer !== undefined) {
658
+ clearTimeout(this.memoryHealthRefreshTimer);
659
+ this.memoryHealthRefreshTimer = undefined;
660
+ }
661
+ }
662
+ invalidateMemoryHealthRefresh() {
663
+ this.memoryHealthRefreshLoopActive = false;
664
+ this.clearMemoryHealthRefreshTimer();
665
+ this.memoryHealthGeneration += 1;
666
+ this.memoryHealthRefreshInFlight = undefined;
667
+ this.memoryHealthLastCompletedAtMs = undefined;
668
+ this.memoryHealthRefreshDue = false;
669
+ }
670
+ /**
671
+ * Start the interaction bridge once, when a blocking ask tool is allowed by
672
+ * the tool policy, a request-context MCP needs scoped progress, or the operator
673
+ * configured the `interaction` block. The master bearer remains app-owned.
412
674
  */
413
675
  ensureInteractionBridge(coreConfig) {
414
676
  this.interactionBridgeStart ??= (async () => {
415
- const askUserAllowed = isAdapterSendToolAllowed("AskUser", {
677
+ const directOpenCodeRoute = runtimeRouteContainsDirectOpenCode(coreConfig);
678
+ const settings = await loadInteractionSettings({ env: this.env, configPath: this.configPath });
679
+ const askUserAllowed = !directOpenCodeRoute && isAdapterSendToolAllowed("AskUser", {
416
680
  allowedTools: coreConfig.tools.allowedTools,
417
681
  disallowedTools: coreConfig.tools.disallowedTools,
418
682
  });
419
- const settings = await loadInteractionSettings({ env: this.env, configPath: this.configPath });
420
- if (!askUserAllowed && !settings.configured) {
683
+ const telegramAskAllowed = !directOpenCodeRoute && isAdapterSendToolAllowed("TelegramAskButtons", {
684
+ allowedTools: coreConfig.tools.allowedTools,
685
+ disallowedTools: coreConfig.tools.disallowedTools,
686
+ });
687
+ const scopedProgressNeeded = settings.progressEnabled
688
+ && (coreConfig.tools.mcpRequestContextServers?.length ?? 0) > 0;
689
+ if (!askUserAllowed && !telegramAskAllowed && !scopedProgressNeeded && !settings.configured) {
421
690
  return undefined;
422
691
  }
423
692
  try {
@@ -427,12 +696,6 @@ class MonoAgentAppController {
427
696
  askTimeoutMs: settings.askTimeoutMs,
428
697
  ...(this.logger === undefined ? {} : { logger: this.logger }),
429
698
  });
430
- const bridgeEnv = bridge.env();
431
- this.interactionBridgeEnvKeys = Object.keys(bridgeEnv);
432
- Object.assign(this.env, bridgeEnv);
433
- if (this.env !== process.env) {
434
- Object.assign(process.env, bridgeEnv);
435
- }
436
699
  this.interactionBridge = bridge;
437
700
  this.logger?.info?.("Interaction bridge started.", { url: bridge.url });
438
701
  return bridge;
@@ -450,13 +713,6 @@ class MonoAgentAppController {
450
713
  const bridge = this.interactionBridge;
451
714
  this.interactionBridge = undefined;
452
715
  this.interactionBridgeStart = undefined;
453
- for (const key of this.interactionBridgeEnvKeys) {
454
- delete this.env[key];
455
- if (this.env !== process.env) {
456
- delete process.env[key];
457
- }
458
- }
459
- this.interactionBridgeEnvKeys = [];
460
716
  await bridge?.stop().catch(() => undefined);
461
717
  }
462
718
  async stop() {
@@ -464,6 +720,9 @@ class MonoAgentAppController {
464
720
  return;
465
721
  }
466
722
  this.stopped = true;
723
+ // Stop the periodic audit before the first teardown await. Already-entered
724
+ // computation is generation-fenced and must never delay shutdown.
725
+ this.invalidateMemoryHealthRefresh();
467
726
  for (const driver of this.drivers) {
468
727
  await this.stopChannel(driver.id, "stop");
469
728
  }
@@ -779,35 +1038,85 @@ class MonoAgentAppController {
779
1038
  if (!this.activeRuntimes.includes(runtime)) {
780
1039
  this.activeRuntimes.push(runtime);
781
1040
  }
782
- const memory = await this.memoryStore(coreConfig);
783
- const memoryRecall = this.memoryRecallRuntimeOptions(coreConfig);
1041
+ const memoryBackend = await this.memoryStore(coreConfig);
1042
+ const memoryRetrieval = this.ensureSharedMemoryRetrieval(coreConfig, memoryBackend);
1043
+ const memory = memoryRetrieval ?? memoryBackend;
1044
+ const memoryRecallEnabled = this.reportMemoryRecallStatus(coreConfig, memoryRetrieval);
784
1045
  const supermemoryMcp = this.supermemoryMcpRuntimeOptions(coreConfig);
785
1046
  const adapterSendTools = await this.adapterSendToolsRuntimeOptions(coreConfig);
1047
+ const runHistoryBase = isRunHistoryToolAllowed(coreConfig.tools)
1048
+ && !runtimeRouteContainsDirectOpenCode(coreConfig)
1049
+ ? createRunHistoryRuntimeExtension({
1050
+ artifactDir: coreConfig.artifacts.dir,
1051
+ onUnavailable: (error) => {
1052
+ this.logger?.warn?.("RunHistory tool endpoint could not start; continuing without prior-run inspection.", {
1053
+ reason: reasonOf(error),
1054
+ });
1055
+ },
1056
+ })
1057
+ : undefined;
786
1058
  // Always active: a no-op for interactive turns (which carry no cron/webhook
787
1059
  // metadata), it applies the per-trigger model/effort override otherwise.
788
- const requestModelOverride = this.requestModelOverrideRuntimeOptions(coreConfig);
1060
+ const mcpSources = [];
1061
+ if (coreConfig.tools.mcpConfigPath !== undefined) {
1062
+ try {
1063
+ const names = Object.keys(loadToolPolicyFromJsonFileSync(coreConfig.tools.mcpConfigPath).mcpServers ?? {});
1064
+ if (names.length > 0)
1065
+ mcpSources.push(`tools.mcpConfigPath (${names.join(", ")})`);
1066
+ }
1067
+ catch {
1068
+ // Responder construction owns the missing/malformed policy error.
1069
+ }
1070
+ }
1071
+ if (memoryRecallEnabled)
1072
+ mcpSources.push("memory.recallTool");
1073
+ if (supermemoryMcp !== undefined)
1074
+ mcpSources.push("memory.supermemory.exposeMcpServer");
1075
+ if (adapterSendTools.blockingToolNames.length > 0) {
1076
+ mcpSources.push(`adapter send tools (${adapterSendTools.blockingToolNames.join(", ")})`);
1077
+ }
1078
+ const requestModelOverride = this.requestModelOverrideRuntimeOptions(coreConfig, {
1079
+ mcpSources,
1080
+ indexSkillsActive: coreConfig.context.skillDisclosure === "index"
1081
+ && coreConfig.context.skillsRoot !== undefined,
1082
+ });
1083
+ const adapterSendToolsExtension = adapterSendTools.createExtension?.(requestModelOverride.targetsDirectOpenCode);
1084
+ const runHistoryExtension = runHistoryBase === undefined
1085
+ ? undefined
1086
+ : async (requestInput) => requestModelOverride.targetsDirectOpenCode(requestInput.request.metadata)
1087
+ ? { runtimeOptions: {}, cleanup: async () => { } }
1088
+ : await runHistoryBase(requestInput);
789
1089
  const runtimeOptionsForRequest = composeRuntimeOptionExtensions([
790
- memoryRecall,
791
1090
  supermemoryMcp,
792
- adapterSendTools,
793
- requestModelOverride,
1091
+ runHistoryExtension,
1092
+ adapterSendToolsExtension,
1093
+ requestModelOverride.extension,
794
1094
  ]);
795
1095
  // The override factory is only needed when fallbacks are configured: the
796
1096
  // fallback router freezes the model chain, so an override must run on a runtime
797
1097
  // whose chain has it as primary. With no fallbacks the shared (plain) runtime
798
1098
  // honors the per-run model directly, so building a separate runtime would be
799
1099
  // redundant. Omit the factory there and the harness uses the shared runtime.
800
- const runtimeForModel = (coreConfig.runtime.fallbackModels?.length ?? 0) > 0
1100
+ const runtimeForModel = hasConfiguredRuntimeFallbacks(coreConfig.runtime)
801
1101
  ? this.buildRuntimeForModel(coreConfig)
802
1102
  : undefined;
803
1103
  const observabilityContext = await this.observabilityContext();
804
1104
  const responder = await createConfiguredAgentResponder({
805
1105
  config: coreConfig,
1106
+ cwd: this.cwd,
806
1107
  runtime,
807
1108
  ...(runtimeForModel === undefined ? {} : { runtimeForModel }),
808
1109
  ...(this.sandboxEngine === undefined ? {} : { sandboxEngine: this.sandboxEngine }),
809
1110
  ...(memory !== undefined && { memory }),
1111
+ ...(this.interactionBridge === undefined ? {} : { turnHistoryEnricher: this.interactionBridge }),
1112
+ ...(this.interactionBridge === undefined ? {} : { progressCapabilityIssuer: this.interactionBridge }),
810
1113
  ...(runtimeOptionsForRequest === undefined ? {} : { runtimeOptionsForRequest }),
1114
+ onMemoryRecallUnavailable: (error) => {
1115
+ this.logger?.warn?.("MemoryRecall tool endpoint could not start; continuing without the explicit tool.", { error: reasonOf(error) });
1116
+ },
1117
+ onMemoryWarning: (message) => {
1118
+ this.logger?.warn?.(message);
1119
+ },
811
1120
  // Thread run-identifying context onto exported spans and surface per-run
812
1121
  // export warnings to `exporterStatus` (agent-host only builds the exporter
813
1122
  // when config.observability.exporters is non-empty).
@@ -827,12 +1136,26 @@ class MonoAgentAppController {
827
1136
  * block for the OVERRIDE model (see request-model-override doc). Composed
828
1137
  * alongside the memory/adapter extensions.
829
1138
  */
830
- requestModelOverrideRuntimeOptions(coreConfig) {
831
- const extension = createRequestModelOverrideRuntimeExtension({
1139
+ requestModelOverrideRuntimeOptions(coreConfig, compatibility) {
1140
+ const options = {
832
1141
  ...(this.logger === undefined ? {} : { logger: this.logger }),
1142
+ baseModel: coreConfig.runtime.model,
1143
+ ...(configuredRuntimeFallbackModels(coreConfig.runtime).length === 0
1144
+ ? {}
1145
+ : { fallbackModels: configuredRuntimeFallbackModels(coreConfig.runtime) }),
1146
+ ...(coreConfig.runtime.effort === undefined ? {} : { baseEffort: coreConfig.runtime.effort }),
1147
+ ...(coreConfig.runtime.maxTurns === undefined ? {} : { baseMaxTurns: coreConfig.runtime.maxTurns }),
1148
+ ...(compatibility.mcpSources.length === 0 ? {} : { mcpSources: compatibility.mcpSources }),
1149
+ ...(compatibility.indexSkillsActive ? { indexSkillsActive: true } : {}),
1150
+ ...(coreConfig.sandbox === undefined ? {} : { sandboxPolicy: coreConfig.sandbox }),
1151
+ toolPolicy: coreConfig.tools,
833
1152
  ...(coreConfig.providers?.local === undefined ? {} : { localProviders: coreConfig.providers.local }),
834
- });
835
- return async (input) => extension({ request: input.request });
1153
+ };
1154
+ const extension = createRequestModelOverrideRuntimeExtension(options);
1155
+ return {
1156
+ extension: async (input) => extension({ request: input.request }),
1157
+ targetsDirectOpenCode: (metadata) => requestModelOverrideTargetsDirectOpenCode(metadata, options),
1158
+ };
836
1159
  }
837
1160
  /**
838
1161
  * Memoized factory for runtimes bound to a per-request override model. Reuses
@@ -875,15 +1198,19 @@ class MonoAgentAppController {
875
1198
  ]);
876
1199
  return { sourceId, sourceLabel, configPath: this.configPath };
877
1200
  }
878
- memoryRecallRuntimeOptions(coreConfig) {
1201
+ reportMemoryRecallStatus(coreConfig, service) {
879
1202
  const settings = resolveMemoryRecallSettings(coreConfig);
880
1203
  if (settings === undefined) {
881
- return undefined;
1204
+ return false;
1205
+ }
1206
+ if (service === undefined) {
1207
+ this.logger?.warn?.("MemoryRecall could not be enabled because the configured store has no recall surface.");
1208
+ return false;
882
1209
  }
883
1210
  this.logger?.info?.("Read-only MemoryRecall tool enabled.", {
884
1211
  provider: "supermemory" in settings ? "supermemory" : settings.embeddings?.provider ?? "fts-only",
885
1212
  });
886
- return createMemoryRecallRuntimeExtension(settings, this.cwd);
1213
+ return true;
887
1214
  }
888
1215
  /**
889
1216
  * Optional CLOUD-ONLY escape hatch: when `memory.supermemory.exposeMcpServer` is on, ALSO inject
@@ -914,20 +1241,47 @@ class MonoAgentAppController {
914
1241
  }
915
1242
  async adapterSendToolsRuntimeOptions(coreConfig) {
916
1243
  const input = { env: this.env, cwd: this.cwd, configPath: this.configPath };
1244
+ const bridgeEnv = this.interactionBridge?.env();
1245
+ const appOwnedInteraction = this.interactionBridge === undefined || bridgeEnv === undefined
1246
+ ? undefined
1247
+ : {
1248
+ bridgeUrl: this.interactionBridge.url,
1249
+ bridgeToken: this.interactionBridge.token,
1250
+ timeoutMs: Number(bridgeEnv.MONO_AGENT_ASK_USER_TIMEOUT_MS),
1251
+ };
917
1252
  const settings = await resolveAdapterSendToolsSettings(input, {
918
1253
  allowedTools: coreConfig.tools.allowedTools,
919
1254
  disallowedTools: coreConfig.tools.disallowedTools,
920
1255
  logger: this.logger,
1256
+ suppressInteractionTools: runtimeRouteContainsDirectOpenCode(coreConfig),
1257
+ ...(appOwnedInteraction === undefined ? {} : { interaction: appOwnedInteraction }),
921
1258
  });
922
1259
  if (settings === undefined) {
923
- return undefined;
1260
+ return { blockingToolNames: [] };
924
1261
  }
925
1262
  const toolNames = adapterSendToolNames(settings);
1263
+ const blockingToolNames = toolNames.filter((name) => !isInteractionToolName(name));
926
1264
  this.logger?.info?.("Adapter send tools enabled.", { tools: toolNames });
927
1265
  // Forward the posted-message index path so `SlackSendMessage` links each post
928
1266
  // back to the producing conversation (so a later in-thread reply resumes it).
929
1267
  const indexPath = resolvePostedMessageIndexPath(await resolveAppArtifactDir(input));
930
- return createAdapterSendToolsRuntimeExtension(this.configPath, this.cwd, toolNames, indexPath);
1268
+ const interactionForChild = settings.askUser ?? settings.telegram?.askBridge;
1269
+ const runOutputRoot = settings.telegram?.sendTools?.pathScope === "run-output"
1270
+ ? resolve(coreConfig.artifacts.dir, "outbound")
1271
+ : undefined;
1272
+ const createExtension = (targetsDirectOpenCode) => async (requestInput) => {
1273
+ const effectiveToolNames = targetsDirectOpenCode(requestInput.request.metadata)
1274
+ ? toolNames.filter((name) => !isInteractionToolName(name))
1275
+ : toolNames;
1276
+ if (effectiveToolNames.length === 0) {
1277
+ return { runtimeOptions: {}, cleanup: async () => { } };
1278
+ }
1279
+ const effectiveInteraction = effectiveToolNames.some(isInteractionToolName)
1280
+ ? interactionForChild
1281
+ : undefined;
1282
+ return await createAdapterSendToolsRuntimeExtension(this.configPath, this.cwd, effectiveToolNames, indexPath, effectiveInteraction, runOutputRoot)(requestInput);
1283
+ };
1284
+ return { createExtension, blockingToolNames };
931
1285
  }
932
1286
  /** Build the configured memory store once and share it across responders + the ritual scheduler. */
933
1287
  async memoryStore(coreConfig) {
@@ -959,9 +1313,11 @@ class MonoAgentAppController {
959
1313
  runEventSink: this.liveEventBus,
960
1314
  };
961
1315
  this.sharedMemory = await createConfiguredMemory(coreConfig, {
1316
+ cwd: this.cwd,
962
1317
  ...(logger === undefined ? {} : { logger }),
963
1318
  observability,
964
1319
  });
1320
+ this.ensureSharedMemoryRetrieval(coreConfig, this.sharedMemory);
965
1321
  this.sharedMemoryBuilt = true;
966
1322
  return this.sharedMemory;
967
1323
  })();
@@ -976,20 +1332,36 @@ class MonoAgentAppController {
976
1332
  async resetSharedMemory() {
977
1333
  const mem = this.sharedMemory;
978
1334
  this.sharedMemory = undefined;
1335
+ this.sharedMemoryRetrieval?.releaseAllTurns();
1336
+ this.sharedMemoryRetrieval = undefined;
979
1337
  this.sharedMemoryBuilt = false;
980
1338
  this.sharedMemoryBuild = undefined;
981
- if (mem?.flush !== undefined) {
982
- await Promise.resolve(mem.flush()).catch(() => undefined);
983
- }
984
1339
  if (mem?.close !== undefined) {
985
1340
  await Promise.resolve(mem.close()).catch(() => undefined);
986
1341
  }
1342
+ else if (mem?.flush !== undefined) {
1343
+ // Stores without a lifecycle-aware close retain the legacy best-effort
1344
+ // drain. BuJo close owns its bounded shutdown deadline itself.
1345
+ await Promise.resolve(mem.flush()).catch(() => undefined);
1346
+ }
987
1347
  }
988
1348
  /** @internal Test-only seam: seed the shared memory store without going through config. */
989
1349
  __setSharedMemoryForTest(store) {
990
1350
  this.sharedMemory = store;
1351
+ this.sharedMemoryRetrieval = undefined;
991
1352
  this.sharedMemoryBuilt = true;
992
1353
  }
1354
+ ensureSharedMemoryRetrieval(coreConfig, store) {
1355
+ if (this.sharedMemoryRetrieval !== undefined)
1356
+ return this.sharedMemoryRetrieval;
1357
+ if (coreConfig.memory === undefined || !isSharedRecallStore(store))
1358
+ return undefined;
1359
+ this.sharedMemoryRetrieval = new MemoryRetrievalService(store, {
1360
+ maxBytes: coreConfig.memory.maxBytes,
1361
+ source: (coreConfig.memory.backend ?? "bujo") === "supermemory" ? "supermemory" : "memory-bujo",
1362
+ });
1363
+ return this.sharedMemoryRetrieval;
1364
+ }
993
1365
  setStatus(id, status) {
994
1366
  this.statuses.set(id, status);
995
1367
  return status;
@@ -1028,14 +1400,25 @@ class MonoAgentAppController {
1028
1400
  };
1029
1401
  }
1030
1402
  async stopTraceSource(reason) {
1403
+ this.invalidateMemoryHealthRefresh();
1031
1404
  const traceSource = this.traceSource;
1032
1405
  const globalTraceSource = this.globalTraceSource;
1406
+ // Detach before stopping. A health probe may be stalled indefinitely; its
1407
+ // generation/handle checks fence any late result, while a reload is free to
1408
+ // register and refresh a new source without waiting for the old probe.
1409
+ this.traceSource = undefined;
1410
+ this.globalTraceSource = undefined;
1411
+ this.traceRefreshInFlight = undefined;
1412
+ this.traceRefreshDirty = false;
1413
+ this.traceRefreshLatestReason = undefined;
1033
1414
  if (traceSource === undefined && globalTraceSource === undefined) {
1034
1415
  return;
1035
1416
  }
1036
- this.traceSource = undefined;
1037
- this.globalTraceSource = undefined;
1038
- const patch = { metadata: this.traceMetadata(reason), transports: this.activeTransports() };
1417
+ const patch = {
1418
+ metadata: this.traceMetadata(reason),
1419
+ transports: this.activeTransports(),
1420
+ memoryHealth: this.memoryHealthValue,
1421
+ };
1039
1422
  await traceSource?.stop(patch).catch((error) => {
1040
1423
  this.logger?.warn?.("Traceability source stop update failed.", { reason: reasonOf(error) });
1041
1424
  });
@@ -1158,6 +1541,81 @@ class MonoAgentAppController {
1158
1541
  };
1159
1542
  }
1160
1543
  }
1544
+ function traceMemoryHealthFromBujo(report) {
1545
+ return {
1546
+ backend: "bujo",
1547
+ mode: report.mode,
1548
+ status: traceMemoryStatus(report.status),
1549
+ checkedAt: report.checkedAt,
1550
+ issues: report.issues.map(traceMemoryIssue),
1551
+ counts: {
1552
+ pending: report.counts.pending,
1553
+ due: report.counts.due,
1554
+ dead: report.counts.dead,
1555
+ outbox: report.counts.outbox,
1556
+ temporary: report.counts.temporary,
1557
+ memories: report.counts.memories,
1558
+ vectors: report.counts.vectors,
1559
+ missingVectors: report.counts.missingVectors,
1560
+ },
1561
+ };
1562
+ }
1563
+ function traceMemoryStatus(status) {
1564
+ switch (status) {
1565
+ case "healthy":
1566
+ case "in_progress":
1567
+ case "degraded":
1568
+ case "unhealthy":
1569
+ case "unknown":
1570
+ return status;
1571
+ }
1572
+ }
1573
+ function traceMemoryIssue(issue) {
1574
+ switch (issue) {
1575
+ case "manifest_missing":
1576
+ case "manifest_invalid":
1577
+ case "configured_identity_mismatch":
1578
+ case "database_missing":
1579
+ case "database_unavailable":
1580
+ case "native_module_unavailable":
1581
+ case "health_check_failed":
1582
+ case "sqlite_integrity_failed":
1583
+ case "metadata_mismatch":
1584
+ case "fts_mismatch":
1585
+ case "vector_mismatch":
1586
+ case "orphaned_rows":
1587
+ case "canonical_mismatch":
1588
+ case "canonical_invalid":
1589
+ case "mutation_in_progress":
1590
+ case "intake_invalid":
1591
+ case "intake_pending":
1592
+ case "dead_letters":
1593
+ case "outbox_invalid":
1594
+ case "outbox_pending":
1595
+ case "work_stalled":
1596
+ case "temporary_artifacts":
1597
+ case "runtime_missing":
1598
+ case "runtime_stale":
1599
+ case "runtime_invalid":
1600
+ return issue;
1601
+ }
1602
+ }
1603
+ function unknownNoMemoryHealth() {
1604
+ return {
1605
+ backend: "none",
1606
+ status: "unknown",
1607
+ checkedAt: new Date().toISOString(),
1608
+ };
1609
+ }
1610
+ function unknownBujoMemoryHealth(mode) {
1611
+ return {
1612
+ backend: "bujo",
1613
+ mode,
1614
+ status: "unknown",
1615
+ checkedAt: new Date().toISOString(),
1616
+ issues: ["health_check_failed"],
1617
+ };
1618
+ }
1161
1619
  function nextDailyRolloverAt(now, timezone) {
1162
1620
  if (timezone === undefined || timezone.trim().length === 0) {
1163
1621
  const next = new Date(now);
@@ -1224,71 +1682,11 @@ function sandboxStatusFromState(state) {
1224
1682
  function reasonOf(error) {
1225
1683
  return error instanceof Error ? error.message : String(error);
1226
1684
  }
1227
- /**
1228
- * Compose several per-request runtime-options extensions into one. Each extension is invoked per
1229
- * request; mergeable runtime option maps/lists are unioned and their cleanups are chained. Returns
1230
- * `undefined` when no extension is active, so the host omits `runtimeOptionsForRequest` entirely.
1231
- */
1232
- function composeRuntimeOptionExtensions(extensions) {
1233
- const active = extensions.filter((extension) => extension !== undefined);
1234
- if (active.length === 0) {
1235
- return undefined;
1236
- }
1237
- if (active.length === 1) {
1238
- return active[0];
1239
- }
1240
- return async (input) => {
1241
- const results = await Promise.all(active.map((extension) => extension(input)));
1242
- const runtimeOptions = {};
1243
- for (const result of results) {
1244
- mergeRuntimeOptions(runtimeOptions, result.runtimeOptions);
1245
- }
1246
- return {
1247
- runtimeOptions,
1248
- cleanup: async () => {
1249
- // Chain every cleanup; run them all even if one rejects so no extension leaks resources.
1250
- await Promise.all(results.map(async (result) => result.cleanup?.()));
1251
- },
1252
- };
1253
- };
1254
- }
1255
- function mergeRuntimeOptions(target, next) {
1256
- if (next === undefined) {
1257
- return;
1258
- }
1259
- for (const [key, value] of Object.entries(next)) {
1260
- if (value === undefined) {
1261
- continue;
1262
- }
1263
- if (key === "allowedTools" || key === "disallowedTools") {
1264
- target[key] = mergeStringLists(target[key], value);
1265
- continue;
1266
- }
1267
- if (key === "mcpServers") {
1268
- target[key] = {
1269
- ...(isRecord(target[key]) ? target[key] : {}),
1270
- ...(isRecord(value) ? value : {}),
1271
- };
1272
- continue;
1273
- }
1274
- target[key] = value;
1275
- }
1276
- }
1277
- function mergeStringLists(current, next) {
1278
- const out = [];
1279
- for (const list of [current, next]) {
1280
- if (!Array.isArray(list)) {
1281
- continue;
1282
- }
1283
- for (const item of list) {
1284
- if (typeof item === "string" && !out.includes(item)) {
1285
- out.push(item);
1286
- }
1287
- }
1288
- }
1289
- return out;
1685
+ function runtimeRouteContainsDirectOpenCode(config) {
1686
+ return configuredRuntimeModels(config.runtime)
1687
+ .some((model) => model.sdk === "opencode");
1290
1688
  }
1291
- function isRecord(value) {
1292
- return typeof value === "object" && value !== null && !Array.isArray(value);
1689
+ function isInteractionToolName(name) {
1690
+ return name === "AskUser" || name === "TelegramAskButtons";
1293
1691
  }
1294
1692
  //# sourceMappingURL=app.js.map