@jungjaehoon/mama-os 0.20.1 → 0.21.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 (195) hide show
  1. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  2. package/dist/agent/code-act/host-bridge.js +14 -3
  3. package/dist/agent/code-act/host-bridge.js.map +1 -1
  4. package/dist/agent/gateway-tool-executor.d.ts +8 -10
  5. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  6. package/dist/agent/gateway-tool-executor.js +201 -28
  7. package/dist/agent/gateway-tool-executor.js.map +1 -1
  8. package/dist/agent/gateway-tools.md +1 -1
  9. package/dist/agent/os-agent-capabilities.md +13 -12
  10. package/dist/agent/tool-registry.js +1 -1
  11. package/dist/agent/tool-registry.js.map +1 -1
  12. package/dist/agent/types.d.ts +4 -0
  13. package/dist/agent/types.d.ts.map +1 -1
  14. package/dist/agent/types.js.map +1 -1
  15. package/dist/api/graph-api.d.ts.map +1 -1
  16. package/dist/api/graph-api.js +78 -5
  17. package/dist/api/graph-api.js.map +1 -1
  18. package/dist/api/index.d.ts +7 -0
  19. package/dist/api/index.d.ts.map +1 -1
  20. package/dist/api/index.js +5 -1
  21. package/dist/api/index.js.map +1 -1
  22. package/dist/api/operator-handler.d.ts +17 -0
  23. package/dist/api/operator-handler.d.ts.map +1 -0
  24. package/dist/api/operator-handler.js +88 -0
  25. package/dist/api/operator-handler.js.map +1 -0
  26. package/dist/api/report-handler.d.ts +7 -0
  27. package/dist/api/report-handler.d.ts.map +1 -1
  28. package/dist/api/report-handler.js +30 -0
  29. package/dist/api/report-handler.js.map +1 -1
  30. package/dist/api/report-persistence.d.ts +17 -0
  31. package/dist/api/report-persistence.d.ts.map +1 -0
  32. package/dist/api/report-persistence.js +91 -0
  33. package/dist/api/report-persistence.js.map +1 -0
  34. package/dist/cli/commands/start.d.ts.map +1 -1
  35. package/dist/cli/commands/start.js +351 -249
  36. package/dist/cli/commands/start.js.map +1 -1
  37. package/dist/cli/config/config-manager.d.ts.map +1 -1
  38. package/dist/cli/config/config-manager.js +93 -55
  39. package/dist/cli/config/config-manager.js.map +1 -1
  40. package/dist/cli/config/types.d.ts +17 -2
  41. package/dist/cli/config/types.d.ts.map +1 -1
  42. package/dist/cli/config/types.js +4 -0
  43. package/dist/cli/config/types.js.map +1 -1
  44. package/dist/cli/runtime/agent-loop-init.d.ts +1 -0
  45. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  46. package/dist/cli/runtime/agent-loop-init.js +1 -0
  47. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  48. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  49. package/dist/cli/runtime/api-routes-init.js +386 -258
  50. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  51. package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
  52. package/dist/cli/runtime/api-server-init.js +12 -7
  53. package/dist/cli/runtime/api-server-init.js.map +1 -1
  54. package/dist/cli/runtime/connector-init.d.ts +19 -1
  55. package/dist/cli/runtime/connector-init.d.ts.map +1 -1
  56. package/dist/cli/runtime/connector-init.js +32 -2
  57. package/dist/cli/runtime/connector-init.js.map +1 -1
  58. package/dist/cli/runtime/message-router-config.d.ts +4 -0
  59. package/dist/cli/runtime/message-router-config.d.ts.map +1 -0
  60. package/dist/cli/runtime/message-router-config.js +11 -0
  61. package/dist/cli/runtime/message-router-config.js.map +1 -0
  62. package/dist/cli/runtime/scheduler-init.d.ts.map +1 -1
  63. package/dist/cli/runtime/scheduler-init.js +12 -12
  64. package/dist/cli/runtime/scheduler-init.js.map +1 -1
  65. package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
  66. package/dist/connectors/framework/polling-scheduler.js +53 -2
  67. package/dist/connectors/framework/polling-scheduler.js.map +1 -1
  68. package/dist/connectors/kagemusha/query-tools.d.ts.map +1 -1
  69. package/dist/connectors/kagemusha/query-tools.js +14 -3
  70. package/dist/connectors/kagemusha/query-tools.js.map +1 -1
  71. package/dist/db/migrations/wiki-artifacts.d.ts +3 -0
  72. package/dist/db/migrations/wiki-artifacts.d.ts.map +1 -0
  73. package/dist/db/migrations/wiki-artifacts.js +24 -0
  74. package/dist/db/migrations/wiki-artifacts.js.map +1 -0
  75. package/dist/gateways/message-router.d.ts.map +1 -1
  76. package/dist/gateways/message-router.js +15 -3
  77. package/dist/gateways/message-router.js.map +1 -1
  78. package/dist/gateways/types.d.ts +4 -0
  79. package/dist/gateways/types.d.ts.map +1 -1
  80. package/dist/multi-agent/agent-event-bus.d.ts +10 -0
  81. package/dist/multi-agent/agent-event-bus.d.ts.map +1 -1
  82. package/dist/multi-agent/agent-event-bus.js +34 -0
  83. package/dist/multi-agent/agent-event-bus.js.map +1 -1
  84. package/dist/multi-agent/conductor-persona.js +2 -2
  85. package/dist/multi-agent/dashboard-agent-persona.d.ts +6 -2
  86. package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
  87. package/dist/multi-agent/dashboard-agent-persona.js +53 -33
  88. package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
  89. package/dist/multi-agent/memory-agent-persona.d.ts +1 -1
  90. package/dist/multi-agent/memory-agent-persona.d.ts.map +1 -1
  91. package/dist/multi-agent/memory-agent-persona.js +25 -4
  92. package/dist/multi-agent/memory-agent-persona.js.map +1 -1
  93. package/dist/multi-agent/types.d.ts +1 -1
  94. package/dist/multi-agent/wiki-agent-persona.d.ts +7 -1
  95. package/dist/multi-agent/wiki-agent-persona.d.ts.map +1 -1
  96. package/dist/multi-agent/wiki-agent-persona.js +82 -68
  97. package/dist/multi-agent/wiki-agent-persona.js.map +1 -1
  98. package/dist/operator/board-slot-instructions.d.ts +18 -0
  99. package/dist/operator/board-slot-instructions.d.ts.map +1 -0
  100. package/dist/operator/board-slot-instructions.js +52 -0
  101. package/dist/operator/board-slot-instructions.js.map +1 -0
  102. package/dist/operator/connector-delta-repo.d.ts +33 -0
  103. package/dist/operator/connector-delta-repo.d.ts.map +1 -0
  104. package/dist/operator/connector-delta-repo.js +0 -0
  105. package/dist/operator/connector-delta-repo.js.map +1 -0
  106. package/dist/operator/mama-memory-port.d.ts +13 -0
  107. package/dist/operator/mama-memory-port.d.ts.map +1 -0
  108. package/dist/operator/mama-memory-port.js +35 -0
  109. package/dist/operator/mama-memory-port.js.map +1 -0
  110. package/dist/operator/operator-interfaces.d.ts +136 -0
  111. package/dist/operator/operator-interfaces.d.ts.map +1 -0
  112. package/dist/operator/operator-interfaces.js +15 -0
  113. package/dist/operator/operator-interfaces.js.map +1 -0
  114. package/dist/operator/operator-trigger-loop.d.ts +106 -0
  115. package/dist/operator/operator-trigger-loop.d.ts.map +1 -0
  116. package/dist/operator/operator-trigger-loop.js +228 -0
  117. package/dist/operator/operator-trigger-loop.js.map +1 -0
  118. package/dist/operator/report-run.d.ts +78 -0
  119. package/dist/operator/report-run.d.ts.map +1 -0
  120. package/dist/operator/report-run.js +141 -0
  121. package/dist/operator/report-run.js.map +1 -0
  122. package/dist/operator/report-scheduler.d.ts +43 -0
  123. package/dist/operator/report-scheduler.d.ts.map +1 -0
  124. package/dist/operator/report-scheduler.js +87 -0
  125. package/dist/operator/report-scheduler.js.map +1 -0
  126. package/dist/operator/situation-report.d.ts +86 -0
  127. package/dist/operator/situation-report.d.ts.map +1 -0
  128. package/dist/operator/situation-report.js +211 -0
  129. package/dist/operator/situation-report.js.map +1 -0
  130. package/dist/operator/trigger-author.d.ts +45 -0
  131. package/dist/operator/trigger-author.d.ts.map +1 -0
  132. package/dist/operator/trigger-author.js +217 -0
  133. package/dist/operator/trigger-author.js.map +1 -0
  134. package/dist/operator/trigger-evolve.d.ts +31 -0
  135. package/dist/operator/trigger-evolve.d.ts.map +1 -0
  136. package/dist/operator/trigger-evolve.js +51 -0
  137. package/dist/operator/trigger-evolve.js.map +1 -0
  138. package/dist/operator/trigger-fire.d.ts +29 -0
  139. package/dist/operator/trigger-fire.d.ts.map +1 -0
  140. package/dist/operator/trigger-fire.js +41 -0
  141. package/dist/operator/trigger-fire.js.map +1 -0
  142. package/dist/operator/trigger-matcher.d.ts +19 -0
  143. package/dist/operator/trigger-matcher.d.ts.map +1 -0
  144. package/dist/operator/trigger-matcher.js +61 -0
  145. package/dist/operator/trigger-matcher.js.map +1 -0
  146. package/dist/operator/trigger-registry.d.ts +36 -0
  147. package/dist/operator/trigger-registry.d.ts.map +1 -0
  148. package/dist/operator/trigger-registry.js +135 -0
  149. package/dist/operator/trigger-registry.js.map +1 -0
  150. package/dist/operator/trigger-review.d.ts +23 -0
  151. package/dist/operator/trigger-review.d.ts.map +1 -0
  152. package/dist/operator/trigger-review.js +127 -0
  153. package/dist/operator/trigger-review.js.map +1 -0
  154. package/dist/operator/trigger-types.d.ts +84 -0
  155. package/dist/operator/trigger-types.d.ts.map +1 -0
  156. package/dist/operator/trigger-types.js +14 -0
  157. package/dist/operator/trigger-types.js.map +1 -0
  158. package/dist/scheduler/cron-worker.d.ts.map +1 -1
  159. package/dist/scheduler/cron-worker.js +3 -1
  160. package/dist/scheduler/cron-worker.js.map +1 -1
  161. package/dist/security/security-monitor.d.ts.map +1 -1
  162. package/dist/security/security-monitor.js +1 -0
  163. package/dist/security/security-monitor.js.map +1 -1
  164. package/dist/wiki/obsidian-writer.d.ts +1 -1
  165. package/dist/wiki/obsidian-writer.d.ts.map +1 -1
  166. package/dist/wiki/obsidian-writer.js +67 -27
  167. package/dist/wiki/obsidian-writer.js.map +1 -1
  168. package/dist/wiki/path-safety.d.ts +2 -0
  169. package/dist/wiki/path-safety.d.ts.map +1 -0
  170. package/dist/wiki/path-safety.js +40 -0
  171. package/dist/wiki/path-safety.js.map +1 -0
  172. package/dist/wiki/types.d.ts +4 -2
  173. package/dist/wiki/types.d.ts.map +1 -1
  174. package/dist/wiki/types.js +1 -1
  175. package/dist/wiki/types.js.map +1 -1
  176. package/dist/wiki-artifacts/normalization.d.ts +8 -0
  177. package/dist/wiki-artifacts/normalization.d.ts.map +1 -0
  178. package/dist/wiki-artifacts/normalization.js +41 -0
  179. package/dist/wiki-artifacts/normalization.js.map +1 -0
  180. package/dist/wiki-artifacts/types.d.ts +52 -0
  181. package/dist/wiki-artifacts/types.d.ts.map +1 -0
  182. package/dist/wiki-artifacts/types.js +3 -0
  183. package/dist/wiki-artifacts/types.js.map +1 -0
  184. package/dist/wiki-artifacts/wiki-publish-adapter.d.ts +14 -0
  185. package/dist/wiki-artifacts/wiki-publish-adapter.d.ts.map +1 -0
  186. package/dist/wiki-artifacts/wiki-publish-adapter.js +71 -0
  187. package/dist/wiki-artifacts/wiki-publish-adapter.js.map +1 -0
  188. package/package.json +5 -3
  189. package/public/ui/assets/index-BDsaQNGd.js +62 -0
  190. package/public/ui/assets/index-C4URnm_X.css +1 -0
  191. package/public/ui/index.html +13 -0
  192. package/public/viewer/js/utils/api.js +2 -2
  193. package/public/viewer/src/utils/api.ts +9 -2
  194. package/public/viewer/sw.js +1 -1
  195. package/public/viewer/viewer.html +6 -0
@@ -200,46 +200,43 @@ async function registerApiRoutes(params) {
200
200
  }
201
201
  // Wire EventBus to tool executor for agent_notices tool
202
202
  toolExecutor.setAgentEventBus(eventBus);
203
- // ── Report Slots ──────────────────────────────────────────────────────
204
- {
205
- const { broadcastReportUpdate } = await import('../../api/report-handler.js');
206
- // Manual refresh endpoint (kept for compatibility)
207
- apiServer.app.post('/api/report/refresh', auth_middleware_js_1.requireAuth, (_req, res) => {
208
- res.json({ ok: true, message: 'Viewer now renders data directly from Intelligence API' });
209
- });
210
- // Wire report_publish tool to OS agent — only briefing slot
211
- toolExecutor.setReportPublisher((slots) => {
212
- for (const [slotId, html] of Object.entries(slots)) {
213
- if (slotId !== 'briefing')
214
- continue; // only accept briefing slot
215
- apiServer.reportStore.update(slotId, html, 0);
216
- }
217
- broadcastReportUpdate(apiServer.reportSseClients, {
218
- slots: apiServer.reportStore.getAllSorted(),
219
- });
220
- routesLogger.debug(`[Report] Agent published briefing slot`);
221
- eventBus.emit({
222
- type: 'agent:action',
223
- agent: 'dashboard-agent',
224
- action: 'publish',
225
- target: 'briefing',
226
- });
227
- });
228
- // ── Conductor Persona (section injection — non-destructive) ────────
229
- const { ensureConductorPersona } = await import('../../multi-agent/conductor-persona.js');
230
- ensureConductorPersona();
231
- // ── Dashboard Agent ─────────────────────────────────────────────────
232
- const { ensureDashboardPersona } = await import('../../multi-agent/dashboard-agent-persona.js');
233
- ensureDashboardPersona();
234
- routesLogger.debug('[Dashboard Agent] Persona ensured at ~/.mama/personas/dashboard.md');
235
- // Merge code-act MCP server into mama-mcp-config.json
236
- // Makes code_act available to all CLI processes (AgentProcessManager agents)
203
+ const hasEnabledAgentConfig = (agentId) => {
204
+ const agentConfig = config.multi_agent?.agents?.[agentId];
205
+ return Boolean(agentConfig && agentConfig.enabled !== false);
206
+ };
207
+ // ── Report Slots + legacy dashboard/wiki fanout ───────────────────────
208
+ const dashboardAgentConfigured = hasEnabledAgentConfig('dashboard-agent');
209
+ const wikiAgentConfigured = hasEnabledAgentConfig('wiki-agent');
210
+ // Manual refresh endpoint (kept for compatibility)
211
+ apiServer.app.post('/api/report/refresh', auth_middleware_js_1.requireAuth, (_req, res) => {
212
+ res.json({ ok: true, message: 'Viewer now renders data directly from Intelligence API' });
213
+ });
214
+ // ── Conductor Persona (section injection — non-destructive) ────────
215
+ const { ensureConductorPersona } = await import('../../multi-agent/conductor-persona.js');
216
+ ensureConductorPersona();
217
+ if (dashboardAgentConfigured || wikiAgentConfigured) {
218
+ // Merge code-act MCP server into mama-mcp-config.json.
219
+ // Makes code_act available to configured legacy self-paced agents.
237
220
  const codeActServerPath = node_path_1.default.join(__dirname, '../../mcp/code-act-server.js');
238
221
  try {
239
222
  const mamaMcpConfigPath = node_path_1.default.join((0, node_os_1.homedir)(), '.mama', 'mama-mcp-config.json');
240
- const existing = (0, node_fs_1.existsSync)(mamaMcpConfigPath)
241
- ? JSON.parse((0, node_fs_1.readFileSync)(mamaMcpConfigPath, 'utf-8'))
242
- : { mcpServers: {} };
223
+ let existing = { mcpServers: {} };
224
+ if ((0, node_fs_1.existsSync)(mamaMcpConfigPath)) {
225
+ try {
226
+ const parsedConfig = JSON.parse((0, node_fs_1.readFileSync)(mamaMcpConfigPath, 'utf-8'));
227
+ if (parsedConfig && typeof parsedConfig === 'object' && !Array.isArray(parsedConfig)) {
228
+ existing = parsedConfig;
229
+ }
230
+ }
231
+ catch (parseErr) {
232
+ routesLogger.warn('[api-routes-init] Invalid MCP config JSON; recreating code-act entry:', parseErr);
233
+ }
234
+ }
235
+ if (!existing.mcpServers ||
236
+ typeof existing.mcpServers !== 'object' ||
237
+ Array.isArray(existing.mcpServers)) {
238
+ existing.mcpServers = {};
239
+ }
243
240
  existing.mcpServers['code-act'] = {
244
241
  command: 'node',
245
242
  args: [codeActServerPath],
@@ -251,6 +248,19 @@ async function registerApiRoutes(params) {
251
248
  catch (err) {
252
249
  routesLogger.warn('[api-routes-init] Failed to merge code-act into MCP config:', err);
253
250
  }
251
+ }
252
+ // report_publish is a core surface (feeds the /ui operator board), not a
253
+ // multi-agent feature: wire it unconditionally. All slot ids are accepted;
254
+ // size/count caps and loud logging live in createReportPublisher.
255
+ {
256
+ const { createReportPublisher } = await import('../../api/report-handler.js');
257
+ toolExecutor.setReportPublisher(createReportPublisher(apiServer.reportStore, apiServer.reportSseClients));
258
+ }
259
+ if (dashboardAgentConfigured) {
260
+ // ── Dashboard Agent ───────────────────────────────────────────────
261
+ const { ensureDashboardPersona } = await import('../../multi-agent/dashboard-agent-persona.js');
262
+ ensureDashboardPersona();
263
+ routesLogger.debug('[Dashboard Agent] Persona ensured at ~/.mama/personas/dashboard.md');
254
264
  // Dashboard cron: 30-min interval via AgentProcessManager
255
265
  const dashboardPrompt = `You are triggered on a schedule. Before writing anything, determine if an update is needed:
256
266
 
@@ -260,279 +270,397 @@ async function registerApiRoutes(params) {
260
270
  Do not include dashboard_briefing, wiki_compilation, system-audit, or audit-log labels in the context_compile task text; filter those operational summaries after the packet returns.
261
271
  If context_compile is unavailable because there is no active worker envelope, fall back to mama_search once (limit 20).
262
272
  3. If NO substantive decisions or agent alerts exist since the last dashboard publish → respond "NO_UPDATE" and stop. Do NOT call report_publish.
263
- 4. If new substantive information exists analyze it, write a new briefing, and publish via report_publish in the "briefing" slot.
273
+ 4. If new substantive information exists -> analyze it and publish ALL FOUR board slots (briefing, action_required, decisions, pipeline) in a SINGLE report_publish call, using the board HTML vocabulary from your persona.
264
274
  5. Do NOT call mama_save for the briefing; report_publish and agent_activity are the durable operational record.
265
275
 
266
276
  This saves resources. Only publish when there is genuinely new information to report.`;
267
- const runDashboardAgent = async () => {
277
+ // Owner-initiated refresh skips the NO_UPDATE delta gate: an explicit
278
+ // request means "rebuild the board now", not "tell me nothing changed".
279
+ const forcedDashboardPrompt = dashboardPrompt.replace(/3\. If NO substantive decisions[^\n]*\n/, '3. The owner explicitly requested a fresh board: do NOT reply NO_UPDATE. Rebuild and publish even if nothing changed since the last publish.\n');
280
+ const doDashboardRun = async (opts) => {
268
281
  const pm = toolExecutor.getAgentProcessManager();
269
282
  if (!pm) {
270
283
  routesLogger.warn('[Dashboard Agent] AgentProcessManager not available yet');
271
284
  return;
272
285
  }
273
286
  try {
274
- routesLogger.debug('[Dashboard Agent] Checking for updates...');
275
- const { noUpdate } = await executeValidatedRun('dashboard-agent', dashboardPrompt);
276
- if (noUpdate) {
277
- routesLogger.debug('[Dashboard Agent] No changes detected, skipped');
278
- }
279
- else {
280
- routesLogger.debug('[Dashboard Agent] Briefing published');
281
- }
287
+ // console.log on run outcomes: the DebugLogger only surfaces warn/error in the
288
+ // daemon log, and a silent outcome reads as a hang from the outside.
289
+ console.log(`[Dashboard Agent] run started${opts?.force ? ' (owner-forced, delta gate bypassed)' : ''}`);
290
+ const { noUpdate } = await executeValidatedRun('dashboard-agent', opts?.force ? forcedDashboardPrompt : dashboardPrompt);
291
+ console.log(noUpdate
292
+ ? '[Dashboard Agent] no changes detected, publish skipped'
293
+ : '[Dashboard Agent] board published');
282
294
  }
283
295
  catch (err) {
284
296
  routesLogger.error('[Dashboard Agent] Error:', err instanceof Error ? err.message : err);
285
297
  }
286
298
  };
299
+ // Serialize ALL dashboard runs (boot, interval, manual) on one chain -- the shared
300
+ // agent process rejects concurrent requests, so unserialized triggers raced into
301
+ // 'Process is busy' errors (same pattern as CronWorker's executionQueue).
302
+ let dashboardRunChain = Promise.resolve();
303
+ const runDashboardAgent = (opts) => {
304
+ // Trailing catch keeps one failed link from poisoning every future run:
305
+ // a rejected chain would silently skip all subsequent .then() links.
306
+ dashboardRunChain = dashboardRunChain.then(() => doDashboardRun(opts)).catch(() => { });
307
+ return dashboardRunChain;
308
+ };
287
309
  // First run after 10s (let connectors poll first), then every 30 min
288
310
  setTimeout(runDashboardAgent, 10_000);
289
311
  setInterval(runDashboardAgent, 30 * 60 * 1000);
290
- // Manual trigger
312
+ // Manual trigger (owner-forced: bypasses the delta gate)
291
313
  apiServer.app.post('/api/report/agent-refresh', auth_middleware_js_1.requireAuth, async (_req, res) => {
292
- runDashboardAgent().catch(() => { });
293
- res.json({ ok: true, message: 'Dashboard agent triggered' });
314
+ runDashboardAgent({ force: true }).catch(() => { });
315
+ res.json({ ok: true, message: 'Dashboard agent triggered (forced refresh)' });
294
316
  });
295
- // ── Wiki Agent ──────────────────────────────────────────────────────
296
- const wikiConfig = config.wiki;
297
- if (wikiConfig?.enabled && wikiConfig.vaultPath) {
298
- const { ensureWikiPersona } = await import('../../multi-agent/wiki-agent-persona.js');
299
- const { ObsidianWriter } = await import('../../wiki/obsidian-writer.js');
300
- ensureWikiPersona();
301
- const obsWriter = new ObsidianWriter(wikiConfig.vaultPath, wikiConfig.wikiDir || 'wiki');
302
- obsWriter.ensureDirectories();
303
- routesLogger.debug(`[Wiki Agent] Persona ensured, vault: ${obsWriter.getWikiPath()}`);
304
- // Wire Obsidian vault path for CLI tool
305
- const fullWikiPath = obsWriter.getWikiPath();
306
- toolExecutor.setObsidianVaultPath(fullWikiPath);
307
- routesLogger.debug(`[Wiki Agent] Obsidian CLI vault: ${fullWikiPath}`);
308
- // Ensure Obsidian is running for CLI access (macOS only)
317
+ }
318
+ else {
319
+ routesLogger.debug('[Dashboard Agent] Skipped; dashboard-agent is not configured');
320
+ }
321
+ // ── Wiki Agent ──────────────────────────────────────────────────────
322
+ const wikiConfig = config.wiki;
323
+ if (wikiAgentConfigured && wikiConfig?.enabled && wikiConfig.vaultPath) {
324
+ const { ensureWikiPersona } = await import('../../multi-agent/wiki-agent-persona.js');
325
+ const { ObsidianWriter } = await import('../../wiki/obsidian-writer.js');
326
+ ensureWikiPersona();
327
+ const obsWriter = new ObsidianWriter(wikiConfig.vaultPath, wikiConfig.wikiDir || 'wiki');
328
+ obsWriter.ensureDirectories();
329
+ routesLogger.debug(`[Wiki Agent] Persona ensured, vault: ${obsWriter.getWikiPath()}`);
330
+ // Wire Obsidian vault path for CLI tool. The wiki directory itself is what
331
+ // gets registered as an Obsidian vault (agent-facing paths like daily/... are
332
+ // relative to it), so the CLI vault name is the wiki path's basename.
333
+ const fullWikiPath = obsWriter.getWikiPath();
334
+ const obsidianVaultName = node_path_1.default.basename(fullWikiPath);
335
+ toolExecutor.setObsidianVaultPath(fullWikiPath, obsidianVaultName);
336
+ routesLogger.debug(`[Wiki Agent] Obsidian CLI vault: ${fullWikiPath} (vault=${obsidianVaultName})`);
337
+ // Ensure Obsidian is running with the wiki vault open (macOS only).
338
+ // NOTE: obsidian://open?vault= only opens vaults ALREADY registered in
339
+ // Obsidian; registering the wiki directory as a vault is a one-time manual
340
+ // setup step. If it is not registered, the CLI reports unavailable and the
341
+ // agent falls back to wiki_publish (direct file writes still work).
342
+ if (process.platform === 'darwin') {
309
343
  try {
310
- const { execSync: execSyncChild } = await import('child_process');
311
- execSyncChild('pgrep -x Obsidian || open -a Obsidian', {
312
- timeout: 5000,
313
- stdio: 'ignore',
344
+ const { execFile: execFileChild } = await import('child_process');
345
+ execFileChild('open', [`obsidian://open?vault=${encodeURIComponent(obsidianVaultName)}`], { timeout: 5000 }, () => {
346
+ /* non-fatal: CLI will return error, agent falls back to wiki_publish */
314
347
  });
315
348
  }
316
349
  catch {
317
- /* non-fatal: CLI will return error, agent falls back to wiki_publish */
350
+ /* non-fatal */
318
351
  }
319
- // Wire wiki_publish tool to shared gateway executor (used by code-act path)
320
- toolExecutor.setWikiPublisher((pages) => {
321
- for (const page of pages) {
322
- obsWriter.writePage(page);
323
- }
324
- if (pages.length > 0) {
325
- obsWriter.updateIndex(pages);
326
- obsWriter.appendLog('compile', `Published ${pages.length} pages`);
327
- }
328
- routesLogger.debug(`[Wiki Agent] Published ${pages.length} pages to vault`);
329
- eventBus.emit({
330
- type: 'wiki:compiled',
331
- pages: pages.map((p) => p.path || ''),
332
- });
352
+ }
353
+ // Wire wiki_publish tool to shared gateway executor (used by code-act path)
354
+ toolExecutor.setWikiPublisher((pages) => {
355
+ for (const page of pages) {
356
+ obsWriter.writePage(page);
357
+ }
358
+ if (pages.length > 0) {
359
+ obsWriter.updateIndex(pages);
360
+ obsWriter.appendLog('compile', `Published ${pages.length} pages`);
361
+ }
362
+ routesLogger.debug(`[Wiki Agent] Published ${pages.length} pages to vault`);
363
+ eventBus.emit({
364
+ type: 'wiki:compiled',
365
+ pages: pages.map((p) => p.path || ''),
333
366
  });
334
- // Wiki trigger via executeValidatedRun
335
- const runWikiAgent = async () => {
336
- if (!toolExecutor.getAgentProcessManager()) {
337
- routesLogger.warn('[Wiki Agent] AgentProcessManager not available yet');
338
- return;
339
- }
340
- try {
341
- routesLogger.debug('[Wiki Agent] Checking for updates...');
342
- const wikiPrompt = `You are triggered on a schedule. Before writing anything, determine if an update is needed:
367
+ });
368
+ // Wiki trigger via executeValidatedRun
369
+ const doWikiRun = async () => {
370
+ if (!toolExecutor.getAgentProcessManager()) {
371
+ routesLogger.warn('[Wiki Agent] AgentProcessManager not available yet');
372
+ return;
373
+ }
374
+ try {
375
+ routesLogger.debug('[Wiki Agent] Checking for updates...');
376
+ const wikiPrompt = `You are triggered on a schedule. Before writing anything, determine if an update is needed:
343
377
 
344
378
  1. Use agent_notices({limit: 100}) to find the most recent wiki-agent compiled/publish/task_complete notice. Treat that as the last compilation boundary.
345
- 2. Use context_compile first to find recent substantive decisions (limit 30, max_tool_calls 3, strictness "balanced").
379
+ 2. NOVELTY CHECK by recency, not semantics: call mama_search({limit: 30}) with NO query -- that returns the newest decisions in creation order regardless of language or wording (semantic/lexical search misses cross-language items, which caused missed compilations). Compare created_at against the boundary.
380
+ 3. If NO substantive decisions are newer than the boundary -> respond "NO_UPDATE" and stop. Do NOT call obsidian or wiki_publish.
381
+ 4. If new items exist, use context_compile first to gather supporting context (limit 30, max_tool_calls 3, strictness "balanced").
346
382
  Use this exact task text for context_compile: "recent substantive project decisions, task progress, agent alerts, and major changes".
347
383
  Do not include dashboard_briefing, wiki_compilation, system-audit, or audit-log labels in the context_compile task text; filter those operational summaries after the packet returns.
348
- If context_compile is unavailable because there is no active worker envelope, fall back to mama_search once (limit 30).
349
- 3. If NO substantive decisions exist since the last wiki compilation respond "NO_UPDATE" and stop. Do NOT call obsidian or wiki_publish.
350
- 4. If new substantive decisions exist use obsidian("search") to check existing pages, then update or create pages as needed. Clean up duplicates.
351
- 5. Do NOT call mama_save for the compilation; Obsidian/wiki files plus agent_activity are the durable operational record.
384
+ If the packet misses some of the new items from step 2 (it often will for cross-language content), write from the step-2 items directly -- the recency list is authoritative for WHAT is new; the packet only enriches.
385
+ If context_compile is unavailable because there is no active worker envelope, fall back to ONE queried mama_search for enrichment and continue from the step-2 recency list.
386
+ 5. Then follow your persona: APPEND today's daily note (daily/YYYY-MM-DD.md, create with the section skeleton on first write of the day), then promote qualifying lesson candidates into lessons/ pages (search before create; update, never duplicate). Do NOT write per-task status pages.
387
+ 6. Do NOT call mama_save for the compilation; Obsidian/wiki files plus agent_activity are the durable operational record.
352
388
 
353
389
  This saves resources. Only compile when there is genuinely new information to document.`;
354
- const { noUpdate } = await executeValidatedRun('wiki-agent', wikiPrompt, {
355
- requestTimeout: 600_000,
390
+ const { noUpdate } = await executeValidatedRun('wiki-agent', wikiPrompt, {
391
+ requestTimeout: 600_000,
392
+ });
393
+ if (noUpdate) {
394
+ routesLogger.debug('[Wiki Agent] No changes detected, skipped');
395
+ }
396
+ else {
397
+ routesLogger.debug('[Wiki Agent] Compilation complete');
398
+ }
399
+ }
400
+ catch (err) {
401
+ routesLogger.error('[Wiki Agent] Error:', err instanceof Error ? err.message : err);
402
+ }
403
+ };
404
+ // Serialize ALL wiki runs (boot, event-driven, manual) on one chain -- the
405
+ // shared agent process rejects concurrent requests, so the boot run and a
406
+ // manual trigger raced into 'Process is busy' (same pattern as the
407
+ // dashboard agent's runDashboardAgent chain above).
408
+ let wikiRunChain = Promise.resolve();
409
+ const runWikiAgent = () => {
410
+ wikiRunChain = wikiRunChain.then(doWikiRun).catch(() => { });
411
+ return wikiRunChain;
412
+ };
413
+ // Event-driven: compile when extraction completes. Trailing-edge debounce so
414
+ // bursts of extraction:completed events coalesce into one wiki compile run.
415
+ eventBus.onDebounced('extraction:completed', () => runWikiAgent(), 30_000);
416
+ // Promotion feeds the wiki: freshly promoted decisions are exactly the
417
+ // material daily notes and lessons compile from.
418
+ eventBus.onDebounced('memory:promoted', () => runWikiAgent(), 30_000);
419
+ // Emit agent:action notices when wiki pages are compiled
420
+ eventBus.on('wiki:compiled', (event) => {
421
+ if (event.type === 'wiki:compiled') {
422
+ for (const page of event.pages) {
423
+ eventBus.emit({
424
+ type: 'agent:action',
425
+ agent: 'Wiki Agent',
426
+ action: 'compiled',
427
+ target: page,
356
428
  });
357
- if (noUpdate) {
358
- routesLogger.debug('[Wiki Agent] No changes detected, skipped');
359
- }
360
- else {
361
- routesLogger.debug('[Wiki Agent] Compilation complete');
362
- }
363
429
  }
364
- catch (err) {
365
- routesLogger.error('[Wiki Agent] Error:', err instanceof Error ? err.message : err);
430
+ }
431
+ });
432
+ // Manual trigger API
433
+ apiServer.app.post('/api/wiki/compile', auth_middleware_js_1.requireAuth, async (_req, res) => {
434
+ runWikiAgent().catch(() => { });
435
+ res.json({ ok: true, message: 'Wiki compilation triggered' });
436
+ });
437
+ // First run after 15s (let connectors and dashboard agent go first)
438
+ setTimeout(runWikiAgent, 15_000);
439
+ routesLogger.info('[Wiki Agent] Ready — triggers: extraction:completed event, POST /api/wiki/compile');
440
+ }
441
+ // -- Memory Promotion: scheduled observation->decision curation --
442
+ // Owner directive 2026-07-11: the decision layer starved after the 07-03/04
443
+ // backfill because nothing periodically judges connector-ingested business
444
+ // data (the M0 kill switch removed direct LLM extraction, and the memory
445
+ // agent only audits owner conversation turns). This pass has the memory
446
+ // agent promote DURABLE judgments only -- task states stay on the board.
447
+ const memoryAgentConfigured = hasEnabledAgentConfig('memory');
448
+ if (memoryAgentConfigured) {
449
+ const PROMOTION_INTERVAL_MS = Math.max(1, Number(process.env.MAMA_MEMORY_PROMOTION_HOURS) || 6) * 60 * 60 * 1000;
450
+ const PROMOTION_INITIAL_DELAY_MS = 10 * 60 * 1000; // let connectors poll first
451
+ const buildPromotionPrompt = (nowIso) => 'PROMOTION RUN. You are curating durable business memory from recent data. ' +
452
+ `The current time is ${nowIso}.\n` +
453
+ '1. agent_notices({limit: 100}): find your latest promotion notice (action "promoted" ' +
454
+ 'or "no_update") and treat it as the boundary; default to the last 24h when absent.\n' +
455
+ '2. kagemusha_entities({activeOnly: true}) to find the rooms active since the boundary, ' +
456
+ 'then kagemusha_messages({channelId, since: <boundary ISO>}) on the busiest 3-4 rooms.\n' +
457
+ '3. For each candidate judgment, mama_search first to find the existing topic; reuse it ' +
458
+ 'so the evolution chain stays intact.\n' +
459
+ '4. Promote at most 5 durable judgments per run via mama_save, following the PROMOTION RUN ' +
460
+ 'rules in your persona (pricing/scope agreements, standing client preferences, process ' +
461
+ 'rules, recurring risk patterns; NEVER task lifecycle states, greetings, or logistics). ' +
462
+ 'Include scopes (the source channel, and the project when identifiable) and event_date.\n' +
463
+ '5. Finish with exactly PROMOTED <n> or NO_UPDATE.';
464
+ const doPromotionRun = async () => {
465
+ if (!toolExecutor.getAgentProcessManager()) {
466
+ routesLogger.warn('[Memory Promotion] AgentProcessManager not available yet');
467
+ return;
468
+ }
469
+ try {
470
+ const { response, noUpdate } = await executeValidatedRun('memory', buildPromotionPrompt(new Date().toISOString()), { requestTimeout: 600_000 });
471
+ const promotedMatch = response?.match(/PROMOTED\s+(\d+)/);
472
+ const saved = promotedMatch ? Number(promotedMatch[1]) : 0;
473
+ eventBus.emit({
474
+ type: 'agent:action',
475
+ agent: 'Memory Agent',
476
+ action: noUpdate || saved === 0 ? 'no_update' : 'promoted',
477
+ target: `promotion run: ${saved} saved`,
478
+ });
479
+ if (saved > 0) {
480
+ eventBus.emit({ type: 'memory:promoted', saved });
481
+ console.log(`[Memory Promotion] Promoted ${saved} durable judgments`);
366
482
  }
367
- };
368
- // Event-driven: compile when extraction completes (debounced)
369
- eventBus.on('extraction:completed', () => runWikiAgent());
370
- // Emit agent:action notices when wiki pages are compiled
371
- eventBus.on('wiki:compiled', (event) => {
372
- if (event.type === 'wiki:compiled') {
373
- for (const page of event.pages) {
374
- eventBus.emit({
375
- type: 'agent:action',
376
- agent: 'Wiki Agent',
377
- action: 'compiled',
378
- target: page,
379
- });
380
- }
483
+ else {
484
+ routesLogger.debug('[Memory Promotion] Nothing qualified for promotion');
381
485
  }
382
- });
383
- // Manual trigger API
384
- apiServer.app.post('/api/wiki/compile', auth_middleware_js_1.requireAuth, async (_req, res) => {
385
- runWikiAgent().catch(() => { });
386
- res.json({ ok: true, message: 'Wiki compilation triggered' });
387
- });
388
- // First run after 15s (let connectors and dashboard agent go first)
389
- setTimeout(runWikiAgent, 15_000);
390
- routesLogger.info('[Wiki Agent] Ready triggers: extraction:completed event, POST /api/wiki/compile');
391
- }
486
+ }
487
+ catch (err) {
488
+ routesLogger.error('[Memory Promotion] Error:', err instanceof Error ? err.message : err);
489
+ }
490
+ };
491
+ // Serialize all promotion runs on one chain (same 'Process is busy' class
492
+ // as the dashboard and wiki agents).
493
+ let promotionRunChain = Promise.resolve();
494
+ const runMemoryPromotion = () => {
495
+ promotionRunChain = promotionRunChain.then(doPromotionRun).catch(() => { });
496
+ return promotionRunChain;
497
+ };
498
+ setTimeout(runMemoryPromotion, PROMOTION_INITIAL_DELAY_MS);
499
+ setInterval(runMemoryPromotion, PROMOTION_INTERVAL_MS);
500
+ apiServer.app.post('/api/memory/promote', auth_middleware_js_1.requireAuth, (_req, res) => {
501
+ runMemoryPromotion().catch(() => { });
502
+ res.json({ ok: true, message: 'Memory promotion run triggered' });
503
+ });
504
+ console.log(`[Memory Promotion] Ready: every ${PROMOTION_INTERVAL_MS / 3_600_000}h, POST /api/memory/promote`);
392
505
  }
393
506
  // ── Conductor Audit — hourly system health check ──────────────────────
394
- {
395
- const AUDIT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
396
- const AUDIT_INITIAL_DELAY_MS = 5 * 60 * 1000; // 5 min after startup
397
- const auditPrompt = 'Perform a system audit. Read ~/.mama/skills/audit-checklist.md and execute each step. ' +
398
- 'Classify findings as MINOR or MAJOR. ' +
399
- 'For MINOR items: execute the auto-fix command immediately (Bash curl). Do NOT just report — fix it. ' +
400
- 'For MAJOR items: report to human via channel alert. ' +
401
- 'Do NOT save raw audit results or audit logs as mama_save decisions. ' +
402
- 'Only call mama_save when the audit produces a durable policy/remediation decision or reusable lesson; otherwise rely on validation sessions and agent_activity for the audit record.';
403
- const runConductorAudit = async () => {
404
- let auditSession = null;
405
- let auditVersion = 0;
406
- const auditStart = Date.now();
507
+ const AUDIT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
508
+ const AUDIT_INITIAL_DELAY_MS = 5 * 60 * 1000; // 5 min after startup
509
+ // Owner verdict 2026-07-11: the hourly audit re-sent the same MAJOR alert every
510
+ // run because each audit was stateless. agent_notices does not carry audit
511
+ // findings (review feedback on #134), so dedup state lives in a JSON file the
512
+ // conductor reads/writes itself, and the current time is interpolated per run
513
+ // so the 24h comparison is grounded.
514
+ const buildAuditPrompt = (nowIso) => 'Perform a system audit. Read ~/.mama/skills/audit-checklist.md and execute each step. ' +
515
+ 'Classify findings as MINOR or MAJOR. ' +
516
+ 'For MINOR items: execute the auto-fix command immediately (Bash curl). Do NOT just report — fix it. ' +
517
+ 'For MAJOR items: report to human via channel alert, subject to the dedup rule below. ' +
518
+ `Alert dedup (MANDATORY): the current time is ${nowIso}. Read ~/.mama/state/audit-findings.json ` +
519
+ '(it may not exist on the first run). Re-alert a MAJOR finding only if it is NEW (no entry ' +
520
+ 'with the same id), has ESCALATED, or its last_alerted_at is older than 24 hours compared to ' +
521
+ 'the current time; otherwise stay silent about it. Never alert MINOR findings to the owner. ' +
522
+ 'After the audit, Write ~/.mama/state/audit-findings.json with an array of every current ' +
523
+ 'finding: {id (stable kebab-case slug), severity, first_seen, last_seen, last_alerted_at}. ' +
524
+ 'Carry forward first_seen and last_alerted_at from the previous file; set last_alerted_at to ' +
525
+ 'the current time only for findings you actually alerted this run. ' +
526
+ // Owner verdict 2026-07-10: hourly-audit self-notes are memory noise -- the two
527
+ // "durable policy" saves it produced were unscoped near-duplicates polluting
528
+ // global recall. The audit record lives in validation sessions + agent_activity;
529
+ // MAJOR findings reach the owner via the channel alert. No memory writes here.
530
+ 'Do NOT call mama_save under any circumstances: the validation session and agent_activity ' +
531
+ 'are the durable audit record, and repeated audits must not accumulate policy notes in memory.';
532
+ const runConductorAudit = async () => {
533
+ let auditSession = null;
534
+ let auditVersion = 0;
535
+ const auditStart = Date.now();
536
+ try {
537
+ routesLogger.debug('[Conductor Audit] Starting hourly audit...');
407
538
  try {
408
- routesLogger.debug('[Conductor Audit] Starting hourly audit...');
539
+ const ver = sessionsDb ? (0, agent_store_js_1.getLatestVersion)(sessionsDb, 'conductor') : null;
540
+ auditVersion = ver?.version ?? 0;
541
+ auditSession =
542
+ validationService?.startSession('conductor', auditVersion, 'audit', {
543
+ goal: 'hourly system audit',
544
+ }) ?? null;
545
+ }
546
+ catch (bootstrapErr) {
547
+ routesLogger.warn('[Conductor Audit] Failed to initialize audit telemetry:', bootstrapErr);
548
+ auditVersion = 0;
549
+ auditSession = null;
550
+ }
551
+ if (sessionsDb) {
409
552
  try {
410
- const ver = sessionsDb ? (0, agent_store_js_1.getLatestVersion)(sessionsDb, 'conductor') : null;
411
- auditVersion = ver?.version ?? 0;
412
- auditSession =
413
- validationService?.startSession('conductor', auditVersion, 'audit', {
414
- goal: 'hourly system audit',
415
- }) ?? null;
416
- }
417
- catch (bootstrapErr) {
418
- routesLogger.warn('[Conductor Audit] Failed to initialize audit telemetry:', bootstrapErr);
419
- auditVersion = 0;
420
- auditSession = null;
421
- }
422
- if (sessionsDb) {
423
- try {
424
- const startRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
425
- agent_id: 'conductor',
426
- agent_version: auditVersion,
427
- type: 'audit_start',
428
- input_summary: 'Hourly system audit',
429
- run_id: auditSession?.id,
430
- execution_status: 'started',
431
- trigger_reason: 'audit',
432
- });
433
- if (auditSession) {
434
- try {
435
- validationService?.recordRun(auditSession.id, { activityId: startRow.id });
436
- }
437
- catch (telemetryErr) {
438
- routesLogger.warn('[Conductor Audit] Failed to link startup activity to validation session:', telemetryErr);
439
- }
553
+ const startRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
554
+ agent_id: 'conductor',
555
+ agent_version: auditVersion,
556
+ type: 'audit_start',
557
+ input_summary: 'Hourly system audit',
558
+ run_id: auditSession?.id,
559
+ execution_status: 'started',
560
+ trigger_reason: 'audit',
561
+ });
562
+ if (auditSession) {
563
+ try {
564
+ validationService?.recordRun(auditSession.id, { activityId: startRow.id });
440
565
  }
441
- }
442
- catch (telemetryErr) {
443
- routesLogger.warn('[Conductor Audit] Failed to write startup telemetry:', telemetryErr);
444
- }
445
- }
446
- const auditChannelId = `conductor-audit-${Date.now()}`;
447
- await messageRouter.process({
448
- source: 'system',
449
- channelId: auditChannelId,
450
- userId: 'system',
451
- text: auditPrompt,
452
- });
453
- const auditDuration = Date.now() - auditStart;
454
- try {
455
- if (sessionsDb) {
456
- const activityRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
457
- agent_id: 'conductor',
458
- agent_version: auditVersion,
459
- type: 'audit_complete',
460
- input_summary: 'Hourly system audit',
461
- duration_ms: auditDuration,
462
- run_id: auditSession?.id,
463
- execution_status: 'completed',
464
- trigger_reason: 'audit',
465
- });
466
- if (auditSession) {
467
- validationService?.recordRun(auditSession.id, { activityId: activityRow.id });
566
+ catch (telemetryErr) {
567
+ routesLogger.warn('[Conductor Audit] Failed to link startup activity to validation session:', telemetryErr);
468
568
  }
469
569
  }
470
570
  }
471
571
  catch (telemetryErr) {
472
- routesLogger.warn('[Conductor Audit] Failed to write completion telemetry:', telemetryErr);
473
- }
474
- try {
475
- if (auditSession && validationService) {
476
- validationService.finalizeSession(auditSession.id, {
477
- execution_status: 'completed',
478
- metrics: { duration_ms: auditDuration },
479
- });
480
- }
481
- }
482
- catch (telemetryErr) {
483
- routesLogger.warn('[Conductor Audit] Failed to finalize validation session:', telemetryErr);
572
+ routesLogger.warn('[Conductor Audit] Failed to write startup telemetry:', telemetryErr);
484
573
  }
485
- routesLogger.debug('[Conductor Audit] Audit complete');
486
574
  }
487
- catch (err) {
488
- const auditDuration = Date.now() - auditStart;
489
- try {
490
- if (sessionsDb) {
491
- const failRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
492
- agent_id: 'conductor',
493
- agent_version: auditVersion,
494
- type: 'audit_failed',
495
- input_summary: 'Hourly system audit failed',
496
- error_message: err instanceof Error ? err.message : String(err),
497
- duration_ms: auditDuration,
498
- run_id: auditSession?.id,
499
- execution_status: 'failed',
500
- trigger_reason: 'audit',
501
- });
502
- if (auditSession) {
503
- validationService?.recordRun(auditSession.id, { activityId: failRow.id });
504
- }
575
+ const auditChannelId = `conductor-audit-${Date.now()}`;
576
+ await messageRouter.process({
577
+ source: 'system',
578
+ channelId: auditChannelId,
579
+ userId: 'system',
580
+ text: buildAuditPrompt(new Date().toISOString()),
581
+ });
582
+ const auditDuration = Date.now() - auditStart;
583
+ try {
584
+ if (sessionsDb) {
585
+ const activityRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
586
+ agent_id: 'conductor',
587
+ agent_version: auditVersion,
588
+ type: 'audit_complete',
589
+ input_summary: 'Hourly system audit',
590
+ duration_ms: auditDuration,
591
+ run_id: auditSession?.id,
592
+ execution_status: 'completed',
593
+ trigger_reason: 'audit',
594
+ });
595
+ if (auditSession) {
596
+ validationService?.recordRun(auditSession.id, { activityId: activityRow.id });
505
597
  }
506
598
  }
507
- catch (telemetryErr) {
508
- routesLogger.warn('[Conductor Audit] Failed to write failure telemetry:', telemetryErr);
599
+ }
600
+ catch (telemetryErr) {
601
+ routesLogger.warn('[Conductor Audit] Failed to write completion telemetry:', telemetryErr);
602
+ }
603
+ try {
604
+ if (auditSession && validationService) {
605
+ validationService.finalizeSession(auditSession.id, {
606
+ execution_status: 'completed',
607
+ metrics: { duration_ms: auditDuration },
608
+ });
509
609
  }
510
- try {
511
- if (auditSession && validationService) {
512
- validationService.finalizeSession(auditSession.id, {
513
- execution_status: 'failed',
514
- error_message: err instanceof Error ? err.message : String(err),
515
- metrics: { duration_ms: auditDuration },
516
- });
610
+ }
611
+ catch (telemetryErr) {
612
+ routesLogger.warn('[Conductor Audit] Failed to finalize validation session:', telemetryErr);
613
+ }
614
+ routesLogger.debug('[Conductor Audit] Audit complete');
615
+ }
616
+ catch (err) {
617
+ const auditDuration = Date.now() - auditStart;
618
+ try {
619
+ if (sessionsDb) {
620
+ const failRow = (0, agent_store_js_1.logActivity)(sessionsDb, {
621
+ agent_id: 'conductor',
622
+ agent_version: auditVersion,
623
+ type: 'audit_failed',
624
+ input_summary: 'Hourly system audit failed',
625
+ error_message: err instanceof Error ? err.message : String(err),
626
+ duration_ms: auditDuration,
627
+ run_id: auditSession?.id,
628
+ execution_status: 'failed',
629
+ trigger_reason: 'audit',
630
+ });
631
+ if (auditSession) {
632
+ validationService?.recordRun(auditSession.id, { activityId: failRow.id });
517
633
  }
518
634
  }
519
- catch (telemetryErr) {
520
- routesLogger.warn('[Conductor Audit] Failed to finalize failed session:', telemetryErr);
635
+ }
636
+ catch (telemetryErr) {
637
+ routesLogger.warn('[Conductor Audit] Failed to write failure telemetry:', telemetryErr);
638
+ }
639
+ try {
640
+ if (auditSession && validationService) {
641
+ validationService.finalizeSession(auditSession.id, {
642
+ execution_status: 'failed',
643
+ error_message: err instanceof Error ? err.message : String(err),
644
+ metrics: { duration_ms: auditDuration },
645
+ });
521
646
  }
522
- routesLogger.error('[Conductor Audit] Failed:', err instanceof Error ? err.message : String(err));
523
647
  }
524
- };
525
- setTimeout(() => {
526
- runConductorAudit();
527
- setInterval(runConductorAudit, AUDIT_INTERVAL_MS);
528
- }, AUDIT_INITIAL_DELAY_MS);
529
- // Manual trigger
530
- apiServer.app.post('/api/conductor/audit', auth_middleware_js_1.requireAuth, async (_req, res) => {
531
- runConductorAudit().catch(() => { });
532
- res.json({ ok: true, message: 'Conductor audit triggered' });
533
- });
534
- routesLogger.info('[Conductor Audit] Ready — runs every 60 min, POST /api/conductor/audit for manual trigger');
535
- }
648
+ catch (telemetryErr) {
649
+ routesLogger.warn('[Conductor Audit] Failed to finalize failed session:', telemetryErr);
650
+ }
651
+ routesLogger.error('[Conductor Audit] Failed:', err instanceof Error ? err.message : String(err));
652
+ }
653
+ };
654
+ setTimeout(() => {
655
+ runConductorAudit();
656
+ setInterval(runConductorAudit, AUDIT_INTERVAL_MS);
657
+ }, AUDIT_INITIAL_DELAY_MS);
658
+ // Manual trigger
659
+ apiServer.app.post('/api/conductor/audit', auth_middleware_js_1.requireAuth, async (_req, res) => {
660
+ runConductorAudit().catch(() => { });
661
+ res.json({ ok: true, message: 'Conductor audit triggered' });
662
+ });
663
+ routesLogger.info('[Conductor Audit] Ready — runs every 60 min, POST /api/conductor/audit for manual trigger');
536
664
  // ── Memory Agent stats API ────────────────────────────────────────────
537
665
  apiServer.app.get('/api/memory-agent/stats', auth_middleware_js_1.requireAuth, (_req, res) => {
538
666
  const stats = messageRouter.getMemoryAgentStats();
@@ -1187,7 +1315,7 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
1187
1315
  res.sendFile(node_path_1.default.join(publicDir, 'setup.html'));
1188
1316
  });
1189
1317
  apiServer.app.use(express_1.default.static(publicDir, {
1190
- setHeaders: (res) => {
1318
+ setHeaders: (res, _filePath) => {
1191
1319
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
1192
1320
  res.setHeader('Pragma', 'no-cache');
1193
1321
  res.setHeader('Expires', '0');