@jungjaehoon/mama-os 0.22.1 → 0.23.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 (124) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -1
  3. package/dist/agent/agent-loop.d.ts +0 -3
  4. package/dist/agent/agent-loop.d.ts.map +1 -1
  5. package/dist/agent/agent-loop.js +49 -34
  6. package/dist/agent/agent-loop.js.map +1 -1
  7. package/dist/agent/gateway-tool-executor.d.ts +18 -0
  8. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  9. package/dist/agent/gateway-tool-executor.js +185 -6
  10. package/dist/agent/gateway-tool-executor.js.map +1 -1
  11. package/dist/agent/gateway-tools.md +10 -5
  12. package/dist/agent/role-manager.d.ts +22 -1
  13. package/dist/agent/role-manager.d.ts.map +1 -1
  14. package/dist/agent/role-manager.js +39 -2
  15. package/dist/agent/role-manager.js.map +1 -1
  16. package/dist/agent/tool-registry.d.ts.map +1 -1
  17. package/dist/agent/tool-registry.js +35 -5
  18. package/dist/agent/tool-registry.js.map +1 -1
  19. package/dist/agent/types.d.ts +9 -1
  20. package/dist/agent/types.d.ts.map +1 -1
  21. package/dist/agent/types.js.map +1 -1
  22. package/dist/api/graph-api.d.ts.map +1 -1
  23. package/dist/api/graph-api.js +43 -33
  24. package/dist/api/graph-api.js.map +1 -1
  25. package/dist/api/operator-tasks-handler.d.ts.map +1 -1
  26. package/dist/api/operator-tasks-handler.js +6 -2
  27. package/dist/api/operator-tasks-handler.js.map +1 -1
  28. package/dist/cli/commands/start.d.ts.map +1 -1
  29. package/dist/cli/commands/start.js +185 -2
  30. package/dist/cli/commands/start.js.map +1 -1
  31. package/dist/cli/config/config-manager.d.ts +0 -5
  32. package/dist/cli/config/config-manager.d.ts.map +1 -1
  33. package/dist/cli/config/config-manager.js +57 -2
  34. package/dist/cli/config/config-manager.js.map +1 -1
  35. package/dist/cli/config/types.d.ts.map +1 -1
  36. package/dist/cli/config/types.js +35 -0
  37. package/dist/cli/config/types.js.map +1 -1
  38. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  39. package/dist/cli/runtime/agent-loop-init.js +96 -51
  40. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  41. package/dist/cli/runtime/api-routes-init.d.ts +3 -0
  42. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  43. package/dist/cli/runtime/api-routes-init.js +226 -51
  44. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  45. package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
  46. package/dist/cli/runtime/gateway-init.js +5 -0
  47. package/dist/cli/runtime/gateway-init.js.map +1 -1
  48. package/dist/envelope/reactive-config.d.ts.map +1 -1
  49. package/dist/envelope/reactive-config.js +26 -1
  50. package/dist/envelope/reactive-config.js.map +1 -1
  51. package/dist/gateways/message-router.d.ts +18 -1
  52. package/dist/gateways/message-router.d.ts.map +1 -1
  53. package/dist/gateways/message-router.js +155 -22
  54. package/dist/gateways/message-router.js.map +1 -1
  55. package/dist/gateways/telegram.d.ts.map +1 -1
  56. package/dist/gateways/telegram.js +13 -0
  57. package/dist/gateways/telegram.js.map +1 -1
  58. package/dist/gateways/types.d.ts +3 -0
  59. package/dist/gateways/types.d.ts.map +1 -1
  60. package/dist/memory/history-extractor.d.ts +0 -24
  61. package/dist/memory/history-extractor.d.ts.map +1 -1
  62. package/dist/memory/history-extractor.js +0 -132
  63. package/dist/memory/history-extractor.js.map +1 -1
  64. package/dist/memory/save-candidate-extractor.d.ts +3 -0
  65. package/dist/memory/save-candidate-extractor.d.ts.map +1 -1
  66. package/dist/memory/save-candidate-extractor.js +12 -1
  67. package/dist/memory/save-candidate-extractor.js.map +1 -1
  68. package/dist/memory/secret-filter.d.ts +22 -0
  69. package/dist/memory/secret-filter.d.ts.map +1 -0
  70. package/dist/memory/secret-filter.js +77 -0
  71. package/dist/memory/secret-filter.js.map +1 -0
  72. package/dist/observability/code-audit.d.ts +4 -0
  73. package/dist/observability/code-audit.d.ts.map +1 -1
  74. package/dist/observability/code-audit.js +69 -0
  75. package/dist/observability/code-audit.js.map +1 -1
  76. package/dist/operator/briefs.d.ts +26 -0
  77. package/dist/operator/briefs.d.ts.map +1 -0
  78. package/dist/operator/briefs.js +128 -0
  79. package/dist/operator/briefs.js.map +1 -0
  80. package/dist/operator/operator-trigger-loop.d.ts +24 -0
  81. package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
  82. package/dist/operator/operator-trigger-loop.js +71 -12
  83. package/dist/operator/operator-trigger-loop.js.map +1 -1
  84. package/dist/operator/report-carry.d.ts +29 -0
  85. package/dist/operator/report-carry.d.ts.map +1 -0
  86. package/dist/operator/report-carry.js +81 -0
  87. package/dist/operator/report-carry.js.map +1 -0
  88. package/dist/operator/shadow-capture.d.ts +20 -0
  89. package/dist/operator/shadow-capture.d.ts.map +1 -0
  90. package/dist/operator/shadow-capture.js +34 -0
  91. package/dist/operator/shadow-capture.js.map +1 -0
  92. package/dist/operator/situation-report.d.ts +6 -0
  93. package/dist/operator/situation-report.d.ts.map +1 -1
  94. package/dist/operator/situation-report.js +13 -0
  95. package/dist/operator/situation-report.js.map +1 -1
  96. package/dist/operator/task-ledger.d.ts +90 -2
  97. package/dist/operator/task-ledger.d.ts.map +1 -1
  98. package/dist/operator/task-ledger.js +359 -23
  99. package/dist/operator/task-ledger.js.map +1 -1
  100. package/dist/operator/worker-run.d.ts +59 -0
  101. package/dist/operator/worker-run.d.ts.map +1 -0
  102. package/dist/operator/worker-run.js +64 -0
  103. package/dist/operator/worker-run.js.map +1 -0
  104. package/dist/operator/workorder-consumer.d.ts +118 -0
  105. package/dist/operator/workorder-consumer.d.ts.map +1 -0
  106. package/dist/operator/workorder-consumer.js +248 -0
  107. package/dist/operator/workorder-consumer.js.map +1 -0
  108. package/dist/operator/workorder-hooks.d.ts +30 -0
  109. package/dist/operator/workorder-hooks.d.ts.map +1 -0
  110. package/dist/operator/workorder-hooks.js +70 -0
  111. package/dist/operator/workorder-hooks.js.map +1 -0
  112. package/dist/operator/workorder-publishers.d.ts +68 -0
  113. package/dist/operator/workorder-publishers.d.ts.map +1 -0
  114. package/dist/operator/workorder-publishers.js +150 -0
  115. package/dist/operator/workorder-publishers.js.map +1 -0
  116. package/dist/security/security-monitor.d.ts +7 -0
  117. package/dist/security/security-monitor.d.ts.map +1 -1
  118. package/dist/security/security-monitor.js +17 -0
  119. package/dist/security/security-monitor.js.map +1 -1
  120. package/dist/utils/untrusted-content.d.ts +13 -0
  121. package/dist/utils/untrusted-content.d.ts.map +1 -1
  122. package/dist/utils/untrusted-content.js +43 -0
  123. package/dist/utils/untrusted-content.js.map +1 -1
  124. package/package.json +2 -2
@@ -57,12 +57,29 @@ const operator_tasks_handler_js_1 = require("../../api/operator-tasks-handler.js
57
57
  const auth_middleware_js_1 = require("../../api/auth-middleware.js");
58
58
  const utilities_js_1 = require("./utilities.js");
59
59
  const code_audit_js_1 = require("../../observability/code-audit.js");
60
+ const workorder_publishers_js_1 = require("../../operator/workorder-publishers.js");
60
61
  const security_monitor_js_1 = require("../../security/security-monitor.js");
61
62
  const debugLogger = __importStar(require("@jungjaehoon/mama-core/debug-logger"));
62
63
  const { DebugLogger } = debugLogger;
63
64
  const routesLogger = new DebugLogger('api-routes');
64
65
  async function registerApiRoutes(params) {
65
- const { config, apiServer, eventBus, oauthManager: _oauthManager, mamaApi: _mamaApi, messageRouter, agentLoop, toolExecutor, discordGateway, slackGateway, graphHandler, getAdapter, sessionsDb, } = params;
66
+ const { config, apiServer, eventBus, oauthManager: _oauthManager, mamaApi: _mamaApi, messageRouter, agentLoop, toolExecutor, discordGateway, slackGateway, graphHandler, getAdapter, sessionsDb, workOrderConsumer, } = params;
67
+ // ── Stage-2 publisher gate (plan S2-T2) ───────────────────────────────
68
+ // Throws at boot on a malformed flag value (no-fallback: a typo must not
69
+ // silently revert a believed-active migration to legacy).
70
+ const stage2Flag = (0, workorder_publishers_js_1.readStage2Flag)();
71
+ if (stage2Flag !== 'off') {
72
+ console.log(`[stage2] workorder publishers active (flag=${stage2Flag})`);
73
+ }
74
+ const enqueueWorkOrderOrThrow = (workKind, idempotencyKey, payload, priority) => {
75
+ const ledger = toolExecutor.getTaskLedger();
76
+ if (!ledger) {
77
+ throw new Error(`[stage2] TaskLedger unavailable - cannot enqueue ${workKind} workorder`);
78
+ }
79
+ (0, workorder_publishers_js_1.validateWorkOrderPayload)(workKind, payload);
80
+ const wo = ledger.enqueueWorkOrder({ workKind, idempotencyKey, input: payload, priority });
81
+ console.log(`[stage2] enqueued workorder ${workKind}#${wo.id} key=${idempotencyKey}`);
82
+ };
66
83
  // ── Validation Session Service ────────────────────────────────────────
67
84
  const validationService = sessionsDb ? new session_service_js_1.ValidationSessionService(sessionsDb) : null;
68
85
  /**
@@ -261,6 +278,22 @@ async function registerApiRoutes(params) {
261
278
  {
262
279
  const { createReportPublisher } = await import('../../api/report-handler.js');
263
280
  toolExecutor.setReportPublisher(createReportPublisher(apiServer.reportStore, apiServer.reportSseClients));
281
+ // S1-T4 artifact hub: the console READS the board it directs. Same store
282
+ // the publisher writes - the conductor's status answers cite these slots
283
+ // instead of re-deriving state from memory copies.
284
+ toolExecutor.setReportReader(() => {
285
+ const slots = apiServer.reportStore.getAll();
286
+ const projected = {};
287
+ for (const [name, slot] of Object.entries(slots)) {
288
+ projected[name] = {
289
+ html: slot.html,
290
+ updatedAt: Number.isFinite(slot.updatedAt)
291
+ ? new Date(slot.updatedAt).toISOString()
292
+ : null,
293
+ };
294
+ }
295
+ return projected;
296
+ });
264
297
  }
265
298
  if (dashboardAgentConfigured) {
266
299
  // ── Dashboard Agent ───────────────────────────────────────────────
@@ -286,6 +319,23 @@ This saves resources. Only publish when there is genuinely new information to re
286
319
  // request means "rebuild the board now", not "tell me nothing changed".
287
320
  const buildForcedDashboardPrompt = () => buildDashboardPrompt().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');
288
321
  const doDashboardRun = async (opts) => {
322
+ // Stage-2 gate at the FUNCTION level: schedule, boot, manual REST all
323
+ // funnel here, so this single gate covers every entry path (plan F4).
324
+ const boardAction = (0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'board');
325
+ if (boardAction !== 'legacy') {
326
+ try {
327
+ const now = Date.now();
328
+ enqueueWorkOrderOrThrow('board', opts?.force ? (0, workorder_publishers_js_1.boardManualKey)(now) : (0, workorder_publishers_js_1.boardFullKey)(now), { mode: 'full', ...(opts?.force ? { force: true } : {}) }, opts?.force ? 'high' : undefined);
329
+ }
330
+ catch (err) {
331
+ routesLogger.error('[stage2] board enqueue failed:', err instanceof Error ? err.message : err);
332
+ }
333
+ // 'enqueue' (on): legacy stops - an enqueue failure must NOT silently
334
+ // fall back to a legacy run; the next occurrence retries.
335
+ // 'both' (shadow): legacy keeps publishing live below.
336
+ if (boardAction === 'enqueue')
337
+ return;
338
+ }
289
339
  const pm = toolExecutor.getAgentProcessManager();
290
340
  if (!pm) {
291
341
  routesLogger.warn('[Dashboard Agent] AgentProcessManager not available yet');
@@ -364,52 +414,130 @@ This saves resources. Only publish when there is genuinely new information to re
364
414
  return row.n;
365
415
  },
366
416
  };
417
+ // Stage-2 board completion hook (plan D1/E3/G1): the consumer wraps
418
+ // reconcile workorders with the SAME bracket the legacy closure owns,
419
+ // but trace rows from worker runs carry the worker's identity, not
420
+ // agent_id='dashboard-agent' - the re-key goes through details JSON
421
+ // (agent_activity has NO channel_id column; json_extract is the only
422
+ // schema-supported key; agent_id re-keying is a green-test trap).
423
+ if (workOrderConsumer) {
424
+ const { buildWorkerTraceQueries } = await import('../../operator/workorder-hooks.js');
425
+ const workerVerifierDeps = {
426
+ ...verifierDeps,
427
+ ...buildWorkerTraceQueries(sessionsDb, 'worker:board'),
428
+ };
429
+ workOrderConsumer.registerHook('board', {
430
+ before: (wo) => {
431
+ if (wo.payload.mode !== 'reconcile')
432
+ return null;
433
+ return captureSnapshot(workerVerifierDeps, `reconcile:${String(wo.payload.channelKey)}`);
434
+ },
435
+ after: (wo, response, beforeState) => {
436
+ if (wo.payload.mode !== 'reconcile' || !beforeState) {
437
+ // FULL runs are not bracket-verified (plan: reconcile mode only).
438
+ if (response.includes('NO_UPDATE')) {
439
+ console.log('[stage2] board worker: no changes detected, publish skipped');
440
+ }
441
+ return;
442
+ }
443
+ const scope = `reconcile:${String(wo.payload.channelKey)}`;
444
+ const verdict = verifyAfterRun(workerVerifierDeps, beforeState, scope);
445
+ const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
446
+ console.log(`[stage2] ${outcome} channel=${String(wo.payload.channelKey)}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
447
+ try {
448
+ if (sessionsDb) {
449
+ (0, agent_store_js_1.logActivity)(sessionsDb, {
450
+ agent_id: 'workorder-board',
451
+ agent_version: 0,
452
+ type: outcome,
453
+ input_summary: `reconcile ${String(wo.payload.channelKey)}`,
454
+ output_summary: verdict.effects.join('; '),
455
+ execution_status: 'completed',
456
+ trigger_reason: 'workorder',
457
+ });
458
+ }
459
+ }
460
+ catch {
461
+ /* telemetry only */
462
+ }
463
+ if (!verdict.verified) {
464
+ eventBus.emit({
465
+ type: 'agent:action',
466
+ agent: 'Dashboard Agent',
467
+ action: 'reconcile_unverified',
468
+ target: String(wo.payload.channelKey),
469
+ });
470
+ }
471
+ },
472
+ });
473
+ }
367
474
  const reconcileScheduler = new ReconcileScheduler({
368
475
  debounceMs: Number(process.env.MAMA_RECONCILE_DEBOUNCE_MS) || undefined,
369
476
  maxWaitMs: Number(process.env.MAMA_RECONCILE_MAX_WAIT_MS) || undefined,
370
477
  globalMaxPerHour: Number(process.env.MAMA_RECONCILE_MAX_PER_HOUR) || undefined,
371
478
  log: (line) => console.log(line),
372
- run: (channelKey, deltaLines) => boardWriterQueue.push(async () => {
373
- const scope = `reconcile:${channelKey}`;
374
- const before = captureSnapshot(verifierDeps, scope);
375
- const prompt = buildReconcilePrompt({
376
- channelKey,
377
- deltaLines,
378
- todayIso: new Date().toISOString().slice(0, 10),
379
- kagemushaContext,
380
- });
381
- await executeValidatedRun('dashboard-agent', prompt, {
382
- requestTimeout: 300_000,
383
- });
384
- const verdict = verifyAfterRun(verifierDeps, before, scope);
385
- const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
386
- console.log(`[reconcile] ${outcome} channel=${channelKey}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
387
- try {
388
- if (sessionsDb) {
389
- (0, agent_store_js_1.logActivity)(sessionsDb, {
390
- agent_id: 'dashboard-agent',
391
- agent_version: 0,
392
- type: outcome,
393
- input_summary: `reconcile ${channelKey}`,
394
- output_summary: verdict.effects.join('; '),
395
- execution_status: 'completed',
396
- trigger_reason: 'reconcile',
479
+ run: (channelKey, deltaLines) => {
480
+ // Stage-2: at 'on' the reconcile leg becomes a board workorder (its
481
+ // bracket verification moves to the consumer's completion hook);
482
+ // shadow/off keep the legacy bracket-verified path (plan C1).
483
+ if ((0, workorder_publishers_js_1.resolveReconcileAction)(stage2Flag) === 'enqueue') {
484
+ try {
485
+ enqueueWorkOrderOrThrow('board', (0, workorder_publishers_js_1.boardReconcileKey)(channelKey, Date.now()), {
486
+ mode: 'reconcile',
487
+ channelKey,
488
+ deltaLines,
397
489
  });
398
490
  }
491
+ catch (err) {
492
+ routesLogger.error('[stage2] reconcile enqueue failed:', err instanceof Error ? err.message : err);
493
+ // REJECT so ReconcileScheduler restores pendingLines - a
494
+ // resolved failure silently drops this delta (PR bot round).
495
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
496
+ }
497
+ return Promise.resolve();
399
498
  }
400
- catch {
401
- /* telemetry only */
402
- }
403
- if (!verdict.verified) {
404
- // Loud, never blocking (observability over restriction).
405
- eventBus.emit({
406
- type: 'agent:action',
407
- agent: 'Dashboard Agent',
408
- action: 'reconcile_unverified',
409
- target: channelKey,
499
+ return boardWriterQueue.push(async () => {
500
+ const scope = `reconcile:${channelKey}`;
501
+ const before = captureSnapshot(verifierDeps, scope);
502
+ const prompt = buildReconcilePrompt({
503
+ channelKey,
504
+ deltaLines,
505
+ todayIso: new Date().toISOString().slice(0, 10),
506
+ kagemushaContext,
410
507
  });
411
- }
412
- }),
508
+ await executeValidatedRun('dashboard-agent', prompt, {
509
+ requestTimeout: 300_000,
510
+ });
511
+ const verdict = verifyAfterRun(verifierDeps, before, scope);
512
+ const outcome = verdict.verified ? 'reconcile_verified' : 'reconcile_unverified';
513
+ console.log(`[reconcile] ${outcome} channel=${channelKey}${verdict.effects.length > 0 ? ` (${verdict.effects.join('; ')})` : ''}`);
514
+ try {
515
+ if (sessionsDb) {
516
+ (0, agent_store_js_1.logActivity)(sessionsDb, {
517
+ agent_id: 'dashboard-agent',
518
+ agent_version: 0,
519
+ type: outcome,
520
+ input_summary: `reconcile ${channelKey}`,
521
+ output_summary: verdict.effects.join('; '),
522
+ execution_status: 'completed',
523
+ trigger_reason: 'reconcile',
524
+ });
525
+ }
526
+ }
527
+ catch {
528
+ /* telemetry only */
529
+ }
530
+ if (!verdict.verified) {
531
+ // Loud, never blocking (observability over restriction).
532
+ eventBus.emit({
533
+ type: 'agent:action',
534
+ agent: 'Dashboard Agent',
535
+ action: 'reconcile_unverified',
536
+ target: channelKey,
537
+ });
538
+ }
539
+ });
540
+ },
413
541
  });
414
542
  eventBus.on('operator:channel-delta', (event) => {
415
543
  if (event.type === 'operator:channel-delta') {
@@ -484,7 +612,20 @@ This saves resources. Only publish when there is genuinely new information to re
484
612
  });
485
613
  });
486
614
  // Wiki trigger via executeValidatedRun
487
- const doWikiRun = async () => {
615
+ const doWikiRun = async (trigger) => {
616
+ // Stage-2 gate (function level, plan F4). Wiki never enters shadow
617
+ // (resolvePublishAction returns 'legacy' there) - its Obsidian writes
618
+ // have no capture seam.
619
+ if ((0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'wiki') === 'enqueue') {
620
+ try {
621
+ const now = Date.now();
622
+ enqueueWorkOrderOrThrow('wiki', (0, workorder_publishers_js_1.wikiBatchKey)(trigger, now), { batchId: `${now}-${trigger}`, events: [trigger] }, trigger === 'manual' ? 'high' : undefined);
623
+ }
624
+ catch (err) {
625
+ routesLogger.error('[stage2] wiki enqueue failed:', err instanceof Error ? err.message : err);
626
+ }
627
+ return;
628
+ }
488
629
  if (!toolExecutor.getAgentProcessManager()) {
489
630
  routesLogger.warn('[Wiki Agent] AgentProcessManager not available yet');
490
631
  return;
@@ -519,21 +660,29 @@ This saves resources. Only compile when there is genuinely new information to do
519
660
  routesLogger.error('[Wiki Agent] Error:', err instanceof Error ? err.message : err);
520
661
  }
521
662
  };
663
+ // Stage-2 wiki completion hook (plan E4): outcome reading only - the
664
+ // wiki:compiled events flow through the wikiPublisher independently.
665
+ if (workOrderConsumer) {
666
+ const { buildWikiAfterHook } = await import('../../operator/workorder-hooks.js');
667
+ workOrderConsumer.registerHook('wiki', {
668
+ after: buildWikiAfterHook((line) => routesLogger.debug(line)),
669
+ });
670
+ }
522
671
  // Serialize ALL wiki runs (boot, event-driven, manual) on one chain -- the
523
672
  // shared agent process rejects concurrent requests, so the boot run and a
524
673
  // manual trigger raced into 'Process is busy' (same pattern as the
525
674
  // dashboard agent's runDashboardAgent chain above).
526
675
  let wikiRunChain = Promise.resolve();
527
- const runWikiAgent = () => {
528
- wikiRunChain = wikiRunChain.then(doWikiRun).catch(() => { });
676
+ const runWikiAgent = (trigger) => {
677
+ wikiRunChain = wikiRunChain.then(() => doWikiRun(trigger)).catch(() => { });
529
678
  return wikiRunChain;
530
679
  };
531
680
  // Event-driven: compile when extraction completes. Trailing-edge debounce so
532
681
  // bursts of extraction:completed events coalesce into one wiki compile run.
533
- eventBus.onDebounced('extraction:completed', () => runWikiAgent(), 30_000);
682
+ eventBus.onDebounced('extraction:completed', () => runWikiAgent('extraction:completed'), 30_000);
534
683
  // Promotion feeds the wiki: freshly promoted decisions are exactly the
535
684
  // material daily notes and lessons compile from.
536
- eventBus.onDebounced('memory:promoted', () => runWikiAgent(), 30_000);
685
+ eventBus.onDebounced('memory:promoted', () => runWikiAgent('memory:promoted'), 30_000);
537
686
  // Emit agent:action notices when wiki pages are compiled
538
687
  eventBus.on('wiki:compiled', (event) => {
539
688
  if (event.type === 'wiki:compiled') {
@@ -549,11 +698,11 @@ This saves resources. Only compile when there is genuinely new information to do
549
698
  });
550
699
  // Manual trigger API
551
700
  apiServer.app.post('/api/wiki/compile', auth_middleware_js_1.requireAuth, async (_req, res) => {
552
- runWikiAgent().catch(() => { });
701
+ runWikiAgent('manual').catch(() => { });
553
702
  res.json({ ok: true, message: 'Wiki compilation triggered' });
554
703
  });
555
704
  // First run after 15s (let connectors and dashboard agent go first)
556
- setTimeout(runWikiAgent, 15_000);
705
+ setTimeout(() => runWikiAgent('boot'), 15_000);
557
706
  routesLogger.info('[Wiki Agent] Ready — triggers: extraction:completed event, POST /api/wiki/compile');
558
707
  }
559
708
  // -- Memory Promotion: scheduled observation->decision curation --
@@ -579,7 +728,19 @@ This saves resources. Only compile when there is genuinely new information to do
579
728
  'rules, recurring risk patterns; NEVER task lifecycle states, greetings, or logistics). ' +
580
729
  'Include scopes (the source channel, and the project when identifiable) and event_date.\n' +
581
730
  '5. Finish with exactly PROMOTED <n> or NO_UPDATE.';
582
- const doPromotionRun = async () => {
731
+ const doPromotionRun = async (opts) => {
732
+ // Stage-2 gate (function level, plan F4). Promotion never enters shadow
733
+ // - its mama_save writes have no capture seam.
734
+ if ((0, workorder_publishers_js_1.resolvePublishAction)(stage2Flag, 'memory-curation') === 'enqueue') {
735
+ try {
736
+ const now = Date.now();
737
+ enqueueWorkOrderOrThrow('memory-curation', opts?.manual ? (0, workorder_publishers_js_1.promotionManualKey)(now) : (0, workorder_publishers_js_1.promotionKey)(now), { scheduledAt: new Date(now).toISOString() }, opts?.manual ? 'high' : undefined);
738
+ }
739
+ catch (err) {
740
+ routesLogger.error('[stage2] promotion enqueue failed:', err instanceof Error ? err.message : err);
741
+ }
742
+ return;
743
+ }
583
744
  if (!toolExecutor.getAgentProcessManager()) {
584
745
  routesLogger.warn('[Memory Promotion] AgentProcessManager not available yet');
585
746
  return;
@@ -606,17 +767,30 @@ This saves resources. Only compile when there is genuinely new information to do
606
767
  routesLogger.error('[Memory Promotion] Error:', err instanceof Error ? err.message : err);
607
768
  }
608
769
  };
770
+ // Stage-2 promotion completion hook (plan E4): the PROMOTED <n> parse and
771
+ // its event emissions are the wiki ingress chain's second link - losing
772
+ // them in the workorder conversion would silently sever memory:promoted.
773
+ if (workOrderConsumer) {
774
+ const { buildPromotionAfterHook } = await import('../../operator/workorder-hooks.js');
775
+ workOrderConsumer.registerHook('memory-curation', {
776
+ after: buildPromotionAfterHook({
777
+ emitAgentAction: (action, target) => eventBus.emit({ type: 'agent:action', agent: 'Memory Agent', action, target }),
778
+ emitMemoryPromoted: (saved) => eventBus.emit({ type: 'memory:promoted', saved }),
779
+ log: (line) => console.log(line),
780
+ }),
781
+ });
782
+ }
609
783
  // Serialize all promotion runs on one chain (same 'Process is busy' class
610
784
  // as the dashboard and wiki agents).
611
785
  let promotionRunChain = Promise.resolve();
612
- const runMemoryPromotion = () => {
613
- promotionRunChain = promotionRunChain.then(doPromotionRun).catch(() => { });
786
+ const runMemoryPromotion = (opts) => {
787
+ promotionRunChain = promotionRunChain.then(() => doPromotionRun(opts)).catch(() => { });
614
788
  return promotionRunChain;
615
789
  };
616
- setTimeout(runMemoryPromotion, PROMOTION_INITIAL_DELAY_MS);
617
- setInterval(runMemoryPromotion, PROMOTION_INTERVAL_MS);
790
+ setTimeout(() => runMemoryPromotion(), PROMOTION_INITIAL_DELAY_MS);
791
+ setInterval(() => runMemoryPromotion(), PROMOTION_INTERVAL_MS);
618
792
  apiServer.app.post('/api/memory/promote', auth_middleware_js_1.requireAuth, (_req, res) => {
619
- runMemoryPromotion().catch(() => { });
793
+ runMemoryPromotion({ manual: true }).catch(() => { });
620
794
  res.json({ ok: true, message: 'Memory promotion run triggered' });
621
795
  });
622
796
  console.log(`[Memory Promotion] Ready: every ${PROMOTION_INTERVAL_MS / 3_600_000}h, POST /api/memory/promote`);
@@ -638,6 +812,7 @@ This saves resources. Only compile when there is genuinely new information to do
638
812
  config: {
639
813
  telegram: config.telegram,
640
814
  multi_agent: config.multi_agent,
815
+ roles: config.roles,
641
816
  },
642
817
  // Evaluated at audit time: gateway-wiring registers the sender only
643
818
  // for ACTIVE gateways, so a configured target on a dead gateway must