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