@mono-agent/agent-app 0.6.2 → 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 -19
  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 +33 -6
  9. package/dist/adapter-send-tools.d.ts.map +1 -1
  10. package/dist/adapter-send-tools.js +485 -87
  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 +528 -135
  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 +16 -6
  24. package/dist/channels.js.map +1 -1
  25. package/dist/cli.d.ts +76 -4
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +2102 -102
  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 +1197 -101
  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 +25 -1
  69. package/dist/interaction-bridge.d.ts.map +1 -1
  70. package/dist/interaction-bridge.js +281 -19
  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 +117 -2
  127. package/dist/provider-setup.d.ts.map +1 -1
  128. package/dist/provider-setup.js +1505 -49
  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 +33 -2
  186. package/dist/wizard/model-discovery.d.ts.map +1 -1
  187. package/dist/wizard/model-discovery.js +826 -108
  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 +56 -9
  194. package/dist/wizard/prompts.d.ts.map +1 -1
  195. package/dist/wizard/prompts.js +172 -20
  196. package/dist/wizard/prompts.js.map +1 -1
  197. package/dist/wizard/run.d.ts +59 -3
  198. package/dist/wizard/run.d.ts.map +1 -1
  199. package/dist/wizard/run.js +1133 -191
  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,49 +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
+ }
471
+ }
472
+ if (!this.traceRefreshDirty || generation !== this.memoryHealthGeneration
473
+ || this.traceSource !== traceSource) {
474
+ return;
400
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;
500
+ }
501
+ /**
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;
406
669
  }
407
670
  /**
408
671
  * Start the interaction bridge once, when a blocking ask tool is allowed by
409
- * the 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.
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 telegramAskAllowed = isAdapterSendToolAllowed("TelegramAskButtons", {
683
+ const telegramAskAllowed = !directOpenCodeRoute && isAdapterSendToolAllowed("TelegramAskButtons", {
420
684
  allowedTools: coreConfig.tools.allowedTools,
421
685
  disallowedTools: coreConfig.tools.disallowedTools,
422
686
  });
423
- const settings = await loadInteractionSettings({ env: this.env, configPath: this.configPath });
424
- if (!askUserAllowed && !telegramAskAllowed && !settings.configured) {
687
+ const scopedProgressNeeded = settings.progressEnabled
688
+ && (coreConfig.tools.mcpRequestContextServers?.length ?? 0) > 0;
689
+ if (!askUserAllowed && !telegramAskAllowed && !scopedProgressNeeded && !settings.configured) {
425
690
  return undefined;
426
691
  }
427
692
  try {
@@ -431,12 +696,6 @@ class MonoAgentAppController {
431
696
  askTimeoutMs: settings.askTimeoutMs,
432
697
  ...(this.logger === undefined ? {} : { logger: this.logger }),
433
698
  });
434
- const bridgeEnv = bridge.env();
435
- this.interactionBridgeEnvKeys = Object.keys(bridgeEnv);
436
- Object.assign(this.env, bridgeEnv);
437
- if (this.env !== process.env) {
438
- Object.assign(process.env, bridgeEnv);
439
- }
440
699
  this.interactionBridge = bridge;
441
700
  this.logger?.info?.("Interaction bridge started.", { url: bridge.url });
442
701
  return bridge;
@@ -454,13 +713,6 @@ class MonoAgentAppController {
454
713
  const bridge = this.interactionBridge;
455
714
  this.interactionBridge = undefined;
456
715
  this.interactionBridgeStart = undefined;
457
- for (const key of this.interactionBridgeEnvKeys) {
458
- delete this.env[key];
459
- if (this.env !== process.env) {
460
- delete process.env[key];
461
- }
462
- }
463
- this.interactionBridgeEnvKeys = [];
464
716
  await bridge?.stop().catch(() => undefined);
465
717
  }
466
718
  async stop() {
@@ -468,6 +720,9 @@ class MonoAgentAppController {
468
720
  return;
469
721
  }
470
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();
471
726
  for (const driver of this.drivers) {
472
727
  await this.stopChannel(driver.id, "stop");
473
728
  }
@@ -783,35 +1038,85 @@ class MonoAgentAppController {
783
1038
  if (!this.activeRuntimes.includes(runtime)) {
784
1039
  this.activeRuntimes.push(runtime);
785
1040
  }
786
- const memory = await this.memoryStore(coreConfig);
787
- 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);
788
1045
  const supermemoryMcp = this.supermemoryMcpRuntimeOptions(coreConfig);
789
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;
790
1058
  // Always active: a no-op for interactive turns (which carry no cron/webhook
791
1059
  // metadata), it applies the per-trigger model/effort override otherwise.
792
- 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);
793
1089
  const runtimeOptionsForRequest = composeRuntimeOptionExtensions([
794
- memoryRecall,
795
1090
  supermemoryMcp,
796
- adapterSendTools,
797
- requestModelOverride,
1091
+ runHistoryExtension,
1092
+ adapterSendToolsExtension,
1093
+ requestModelOverride.extension,
798
1094
  ]);
799
1095
  // The override factory is only needed when fallbacks are configured: the
800
1096
  // fallback router freezes the model chain, so an override must run on a runtime
801
1097
  // whose chain has it as primary. With no fallbacks the shared (plain) runtime
802
1098
  // honors the per-run model directly, so building a separate runtime would be
803
1099
  // redundant. Omit the factory there and the harness uses the shared runtime.
804
- const runtimeForModel = (coreConfig.runtime.fallbackModels?.length ?? 0) > 0
1100
+ const runtimeForModel = hasConfiguredRuntimeFallbacks(coreConfig.runtime)
805
1101
  ? this.buildRuntimeForModel(coreConfig)
806
1102
  : undefined;
807
1103
  const observabilityContext = await this.observabilityContext();
808
1104
  const responder = await createConfiguredAgentResponder({
809
1105
  config: coreConfig,
1106
+ cwd: this.cwd,
810
1107
  runtime,
811
1108
  ...(runtimeForModel === undefined ? {} : { runtimeForModel }),
812
1109
  ...(this.sandboxEngine === undefined ? {} : { sandboxEngine: this.sandboxEngine }),
813
1110
  ...(memory !== undefined && { memory }),
1111
+ ...(this.interactionBridge === undefined ? {} : { turnHistoryEnricher: this.interactionBridge }),
1112
+ ...(this.interactionBridge === undefined ? {} : { progressCapabilityIssuer: this.interactionBridge }),
814
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
+ },
815
1120
  // Thread run-identifying context onto exported spans and surface per-run
816
1121
  // export warnings to `exporterStatus` (agent-host only builds the exporter
817
1122
  // when config.observability.exporters is non-empty).
@@ -831,12 +1136,26 @@ class MonoAgentAppController {
831
1136
  * block for the OVERRIDE model (see request-model-override doc). Composed
832
1137
  * alongside the memory/adapter extensions.
833
1138
  */
834
- requestModelOverrideRuntimeOptions(coreConfig) {
835
- const extension = createRequestModelOverrideRuntimeExtension({
1139
+ requestModelOverrideRuntimeOptions(coreConfig, compatibility) {
1140
+ const options = {
836
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,
837
1152
  ...(coreConfig.providers?.local === undefined ? {} : { localProviders: coreConfig.providers.local }),
838
- });
839
- 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
+ };
840
1159
  }
841
1160
  /**
842
1161
  * Memoized factory for runtimes bound to a per-request override model. Reuses
@@ -879,15 +1198,19 @@ class MonoAgentAppController {
879
1198
  ]);
880
1199
  return { sourceId, sourceLabel, configPath: this.configPath };
881
1200
  }
882
- memoryRecallRuntimeOptions(coreConfig) {
1201
+ reportMemoryRecallStatus(coreConfig, service) {
883
1202
  const settings = resolveMemoryRecallSettings(coreConfig);
884
1203
  if (settings === undefined) {
885
- 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;
886
1209
  }
887
1210
  this.logger?.info?.("Read-only MemoryRecall tool enabled.", {
888
1211
  provider: "supermemory" in settings ? "supermemory" : settings.embeddings?.provider ?? "fts-only",
889
1212
  });
890
- return createMemoryRecallRuntimeExtension(settings, this.cwd);
1213
+ return true;
891
1214
  }
892
1215
  /**
893
1216
  * Optional CLOUD-ONLY escape hatch: when `memory.supermemory.exposeMcpServer` is on, ALSO inject
@@ -918,21 +1241,47 @@ class MonoAgentAppController {
918
1241
  }
919
1242
  async adapterSendToolsRuntimeOptions(coreConfig) {
920
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
+ };
921
1252
  const settings = await resolveAdapterSendToolsSettings(input, {
922
1253
  allowedTools: coreConfig.tools.allowedTools,
923
1254
  disallowedTools: coreConfig.tools.disallowedTools,
924
1255
  logger: this.logger,
1256
+ suppressInteractionTools: runtimeRouteContainsDirectOpenCode(coreConfig),
1257
+ ...(appOwnedInteraction === undefined ? {} : { interaction: appOwnedInteraction }),
925
1258
  });
926
1259
  if (settings === undefined) {
927
- return undefined;
1260
+ return { blockingToolNames: [] };
928
1261
  }
929
1262
  const toolNames = adapterSendToolNames(settings);
1263
+ const blockingToolNames = toolNames.filter((name) => !isInteractionToolName(name));
930
1264
  this.logger?.info?.("Adapter send tools enabled.", { tools: toolNames });
931
1265
  // Forward the posted-message index path so `SlackSendMessage` links each post
932
1266
  // back to the producing conversation (so a later in-thread reply resumes it).
933
1267
  const indexPath = resolvePostedMessageIndexPath(await resolveAppArtifactDir(input));
934
- const interaction = settings.askUser ?? settings.telegram?.askBridge;
935
- return createAdapterSendToolsRuntimeExtension(this.configPath, this.cwd, toolNames, indexPath, interaction);
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 };
936
1285
  }
937
1286
  /** Build the configured memory store once and share it across responders + the ritual scheduler. */
938
1287
  async memoryStore(coreConfig) {
@@ -964,9 +1313,11 @@ class MonoAgentAppController {
964
1313
  runEventSink: this.liveEventBus,
965
1314
  };
966
1315
  this.sharedMemory = await createConfiguredMemory(coreConfig, {
1316
+ cwd: this.cwd,
967
1317
  ...(logger === undefined ? {} : { logger }),
968
1318
  observability,
969
1319
  });
1320
+ this.ensureSharedMemoryRetrieval(coreConfig, this.sharedMemory);
970
1321
  this.sharedMemoryBuilt = true;
971
1322
  return this.sharedMemory;
972
1323
  })();
@@ -981,20 +1332,36 @@ class MonoAgentAppController {
981
1332
  async resetSharedMemory() {
982
1333
  const mem = this.sharedMemory;
983
1334
  this.sharedMemory = undefined;
1335
+ this.sharedMemoryRetrieval?.releaseAllTurns();
1336
+ this.sharedMemoryRetrieval = undefined;
984
1337
  this.sharedMemoryBuilt = false;
985
1338
  this.sharedMemoryBuild = undefined;
986
- if (mem?.flush !== undefined) {
987
- await Promise.resolve(mem.flush()).catch(() => undefined);
988
- }
989
1339
  if (mem?.close !== undefined) {
990
1340
  await Promise.resolve(mem.close()).catch(() => undefined);
991
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
+ }
992
1347
  }
993
1348
  /** @internal Test-only seam: seed the shared memory store without going through config. */
994
1349
  __setSharedMemoryForTest(store) {
995
1350
  this.sharedMemory = store;
1351
+ this.sharedMemoryRetrieval = undefined;
996
1352
  this.sharedMemoryBuilt = true;
997
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
+ }
998
1365
  setStatus(id, status) {
999
1366
  this.statuses.set(id, status);
1000
1367
  return status;
@@ -1033,14 +1400,25 @@ class MonoAgentAppController {
1033
1400
  };
1034
1401
  }
1035
1402
  async stopTraceSource(reason) {
1403
+ this.invalidateMemoryHealthRefresh();
1036
1404
  const traceSource = this.traceSource;
1037
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;
1038
1414
  if (traceSource === undefined && globalTraceSource === undefined) {
1039
1415
  return;
1040
1416
  }
1041
- this.traceSource = undefined;
1042
- this.globalTraceSource = undefined;
1043
- 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
+ };
1044
1422
  await traceSource?.stop(patch).catch((error) => {
1045
1423
  this.logger?.warn?.("Traceability source stop update failed.", { reason: reasonOf(error) });
1046
1424
  });
@@ -1163,6 +1541,81 @@ class MonoAgentAppController {
1163
1541
  };
1164
1542
  }
1165
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
+ }
1166
1619
  function nextDailyRolloverAt(now, timezone) {
1167
1620
  if (timezone === undefined || timezone.trim().length === 0) {
1168
1621
  const next = new Date(now);
@@ -1229,71 +1682,11 @@ function sandboxStatusFromState(state) {
1229
1682
  function reasonOf(error) {
1230
1683
  return error instanceof Error ? error.message : String(error);
1231
1684
  }
1232
- /**
1233
- * Compose several per-request runtime-options extensions into one. Each extension is invoked per
1234
- * request; mergeable runtime option maps/lists are unioned and their cleanups are chained. Returns
1235
- * `undefined` when no extension is active, so the host omits `runtimeOptionsForRequest` entirely.
1236
- */
1237
- function composeRuntimeOptionExtensions(extensions) {
1238
- const active = extensions.filter((extension) => extension !== undefined);
1239
- if (active.length === 0) {
1240
- return undefined;
1241
- }
1242
- if (active.length === 1) {
1243
- return active[0];
1244
- }
1245
- return async (input) => {
1246
- const results = await Promise.all(active.map((extension) => extension(input)));
1247
- const runtimeOptions = {};
1248
- for (const result of results) {
1249
- mergeRuntimeOptions(runtimeOptions, result.runtimeOptions);
1250
- }
1251
- return {
1252
- runtimeOptions,
1253
- cleanup: async () => {
1254
- // Chain every cleanup; run them all even if one rejects so no extension leaks resources.
1255
- await Promise.all(results.map(async (result) => result.cleanup?.()));
1256
- },
1257
- };
1258
- };
1259
- }
1260
- function mergeRuntimeOptions(target, next) {
1261
- if (next === undefined) {
1262
- return;
1263
- }
1264
- for (const [key, value] of Object.entries(next)) {
1265
- if (value === undefined) {
1266
- continue;
1267
- }
1268
- if (key === "allowedTools" || key === "disallowedTools") {
1269
- target[key] = mergeStringLists(target[key], value);
1270
- continue;
1271
- }
1272
- if (key === "mcpServers") {
1273
- target[key] = {
1274
- ...(isRecord(target[key]) ? target[key] : {}),
1275
- ...(isRecord(value) ? value : {}),
1276
- };
1277
- continue;
1278
- }
1279
- target[key] = value;
1280
- }
1281
- }
1282
- function mergeStringLists(current, next) {
1283
- const out = [];
1284
- for (const list of [current, next]) {
1285
- if (!Array.isArray(list)) {
1286
- continue;
1287
- }
1288
- for (const item of list) {
1289
- if (typeof item === "string" && !out.includes(item)) {
1290
- out.push(item);
1291
- }
1292
- }
1293
- }
1294
- return out;
1685
+ function runtimeRouteContainsDirectOpenCode(config) {
1686
+ return configuredRuntimeModels(config.runtime)
1687
+ .some((model) => model.sdk === "opencode");
1295
1688
  }
1296
- function isRecord(value) {
1297
- return typeof value === "object" && value !== null && !Array.isArray(value);
1689
+ function isInteractionToolName(name) {
1690
+ return name === "AskUser" || name === "TelegramAskButtons";
1298
1691
  }
1299
1692
  //# sourceMappingURL=app.js.map