@jungjaehoon/mama-os 0.40.0 → 0.41.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 (109) hide show
  1. package/README.md +2 -2
  2. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  3. package/dist/agent/code-act/host-bridge.js +0 -22
  4. package/dist/agent/code-act/host-bridge.js.map +1 -1
  5. package/dist/agent/gateway-tool-executor.d.ts +6 -7
  6. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  7. package/dist/agent/gateway-tool-executor.js +38 -76
  8. package/dist/agent/gateway-tool-executor.js.map +1 -1
  9. package/dist/agent/gateway-tools.md +1 -3
  10. package/dist/agent/tool-registry.d.ts.map +1 -1
  11. package/dist/agent/tool-registry.js +6 -18
  12. package/dist/agent/tool-registry.js.map +1 -1
  13. package/dist/agent/types.d.ts +1 -8
  14. package/dist/agent/types.d.ts.map +1 -1
  15. package/dist/agent/types.js.map +1 -1
  16. package/dist/cli/commands/start.d.ts +45 -52
  17. package/dist/cli/commands/start.d.ts.map +1 -1
  18. package/dist/cli/commands/start.js +247 -202
  19. package/dist/cli/commands/start.js.map +1 -1
  20. package/dist/cli/config/types.d.ts.map +1 -1
  21. package/dist/cli/config/types.js +0 -2
  22. package/dist/cli/config/types.js.map +1 -1
  23. package/dist/cli/runtime/api-routes-init.d.ts +0 -3
  24. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  25. package/dist/cli/runtime/api-routes-init.js +8 -32
  26. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  27. package/dist/connectors/private-connector-policy.d.ts +1 -3
  28. package/dist/connectors/private-connector-policy.d.ts.map +1 -1
  29. package/dist/connectors/private-connector-policy.js +1 -26
  30. package/dist/connectors/private-connector-policy.js.map +1 -1
  31. package/dist/gateways/message-router.d.ts.map +1 -1
  32. package/dist/gateways/message-router.js +2 -3
  33. package/dist/gateways/message-router.js.map +1 -1
  34. package/dist/multi-agent/dashboard-agent-persona.d.ts +1 -1
  35. package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
  36. package/dist/multi-agent/dashboard-agent-persona.js +6 -7
  37. package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
  38. package/dist/operator/board-pipeline-render.d.ts +17 -0
  39. package/dist/operator/board-pipeline-render.d.ts.map +1 -0
  40. package/dist/operator/board-pipeline-render.js +73 -0
  41. package/dist/operator/board-pipeline-render.js.map +1 -0
  42. package/dist/operator/board-slot-instructions.d.ts +0 -8
  43. package/dist/operator/board-slot-instructions.d.ts.map +1 -1
  44. package/dist/operator/board-slot-instructions.js +3 -37
  45. package/dist/operator/board-slot-instructions.js.map +1 -1
  46. package/dist/operator/console-brief.d.ts +1 -1
  47. package/dist/operator/console-brief.d.ts.map +1 -1
  48. package/dist/operator/console-brief.js +5 -4
  49. package/dist/operator/console-brief.js.map +1 -1
  50. package/dist/operator/owner-event-inbox.d.ts +10 -1
  51. package/dist/operator/owner-event-inbox.d.ts.map +1 -1
  52. package/dist/operator/owner-event-inbox.js +22 -6
  53. package/dist/operator/owner-event-inbox.js.map +1 -1
  54. package/dist/operator/owner-event-loop.d.ts +4 -2
  55. package/dist/operator/owner-event-loop.d.ts.map +1 -1
  56. package/dist/operator/owner-event-loop.js +20 -3
  57. package/dist/operator/owner-event-loop.js.map +1 -1
  58. package/dist/operator/owner-event-outcome.d.ts +3 -3
  59. package/dist/operator/owner-event-outcome.d.ts.map +1 -1
  60. package/dist/operator/owner-event-outcome.js +66 -9
  61. package/dist/operator/owner-event-outcome.js.map +1 -1
  62. package/dist/operator/owner-event-policy.d.ts.map +1 -1
  63. package/dist/operator/owner-event-policy.js +18 -8
  64. package/dist/operator/owner-event-policy.js.map +1 -1
  65. package/dist/operator/owner-event-prompt.d.ts +2 -0
  66. package/dist/operator/owner-event-prompt.d.ts.map +1 -1
  67. package/dist/operator/owner-event-prompt.js +14 -5
  68. package/dist/operator/owner-event-prompt.js.map +1 -1
  69. package/dist/operator/report-context.d.ts +12 -0
  70. package/dist/operator/report-context.d.ts.map +1 -1
  71. package/dist/operator/report-context.js +60 -0
  72. package/dist/operator/report-context.js.map +1 -1
  73. package/dist/operator/task-ledger.d.ts +20 -1
  74. package/dist/operator/task-ledger.d.ts.map +1 -1
  75. package/dist/operator/task-ledger.js +58 -1
  76. package/dist/operator/task-ledger.js.map +1 -1
  77. package/dist/operator/temporal-worker.d.ts +0 -1
  78. package/dist/operator/temporal-worker.d.ts.map +1 -1
  79. package/dist/operator/temporal-worker.js +0 -29
  80. package/dist/operator/temporal-worker.js.map +1 -1
  81. package/dist/operator/worker-run.d.ts +1 -1
  82. package/dist/operator/worker-run.d.ts.map +1 -1
  83. package/dist/operator/worker-run.js +5 -4
  84. package/dist/operator/worker-run.js.map +1 -1
  85. package/dist/operator/workorder-consumer.d.ts +18 -2
  86. package/dist/operator/workorder-consumer.d.ts.map +1 -1
  87. package/dist/operator/workorder-consumer.js +92 -1
  88. package/dist/operator/workorder-consumer.js.map +1 -1
  89. package/dist/operator/workorder-hooks.d.ts +1 -7
  90. package/dist/operator/workorder-hooks.d.ts.map +1 -1
  91. package/dist/operator/workorder-hooks.js +1 -5
  92. package/dist/operator/workorder-hooks.js.map +1 -1
  93. package/dist/operator/workorder-publishers.d.ts +0 -7
  94. package/dist/operator/workorder-publishers.d.ts.map +1 -1
  95. package/dist/operator/workorder-publishers.js +0 -22
  96. package/dist/operator/workorder-publishers.js.map +1 -1
  97. package/package.json +1 -1
  98. package/dist/db/migrations/owner-event-board-refresh.d.ts +0 -10
  99. package/dist/db/migrations/owner-event-board-refresh.d.ts.map +0 -1
  100. package/dist/db/migrations/owner-event-board-refresh.js +0 -27
  101. package/dist/db/migrations/owner-event-board-refresh.js.map +0 -1
  102. package/dist/operator/briefs.d.ts +0 -50
  103. package/dist/operator/briefs.d.ts.map +0 -1
  104. package/dist/operator/briefs.js +0 -451
  105. package/dist/operator/briefs.js.map +0 -1
  106. package/dist/operator/owner-event-board-refresh.d.ts +0 -51
  107. package/dist/operator/owner-event-board-refresh.d.ts.map +0 -1
  108. package/dist/operator/owner-event-board-refresh.js +0 -260
  109. package/dist/operator/owner-event-board-refresh.js.map +0 -1
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.OPERATOR_REPORT_TOOL_POLICY = exports.WORKORDER_TOOL_POLICIES = void 0;
48
+ exports.OPERATOR_REPORT_TOOL_POLICY = exports.TURN_KIND_BLOCKED_TOOLS = exports.SCHEDULED_TURN_BLOCKED_TOOLS = exports.TURN_KIND_REQUIRED_TOOLS = exports.ADMINISTRATION_TOOLS = exports.ONE_AGENT_TURN_POLICY = void 0;
49
49
  exports.workOrderActivityDetails = workOrderActivityDetails;
50
50
  exports.createCorePrincipalRegistry = createCorePrincipalRegistry;
51
51
  exports.createCorePrincipalResolver = createCorePrincipalResolver;
@@ -60,14 +60,13 @@ exports.causeEventIdsFromPayload = causeEventIdsFromPayload;
60
60
  exports.workOrderEnvelopeScope = workOrderEnvelopeScope;
61
61
  exports.temporalTaskBinding = temporalTaskBinding;
62
62
  exports.buildOperatorReportAgentPolicy = buildOperatorReportAgentPolicy;
63
- exports.buildWorkOrderAgentPolicy = buildWorkOrderAgentPolicy;
63
+ exports.isOutboundToolName = isOutboundToolName;
64
+ exports.buildTurnAgentPolicy = buildTurnAgentPolicy;
64
65
  exports.bindCodeActParentModelRun = bindCodeActParentModelRun;
65
66
  exports.finalizeCodeActParentModelRun = finalizeCodeActParentModelRun;
66
67
  exports.failCodeActParentModelRun = failCodeActParentModelRun;
67
68
  exports.buildSystemAgentProcessDefaults = buildSystemAgentProcessDefaults;
68
- exports.buildOwnerEventBoardRefreshRuntime = buildOwnerEventBoardRefreshRuntime;
69
69
  exports.resolveOwnerEventTerminalReceipt = resolveOwnerEventTerminalReceipt;
70
- exports.buildOwnerWorkOrderRequestHandler = buildOwnerWorkOrderRequestHandler;
71
70
  exports.startCommand = startCommand;
72
71
  exports.buildStartOnboardingOutput = buildStartOnboardingOutput;
73
72
  exports.runAgentLoop = runAgentLoop;
@@ -135,11 +134,10 @@ const query_tools_js_1 = require("../../connectors/trello/query-tools.js");
135
134
  const console_brief_js_1 = require("../../operator/console-brief.js");
136
135
  const task_ledger_js_1 = require("../../operator/task-ledger.js");
137
136
  const board_refresh_gate_js_1 = require("../../operator/board-refresh-gate.js");
138
- const workorder_publishers_js_1 = require("../../operator/workorder-publishers.js");
139
137
  const owner_event_inbox_js_1 = require("../../operator/owner-event-inbox.js");
140
138
  const owner_event_effects_js_1 = require("../../operator/owner-event-effects.js");
141
- const owner_event_board_refresh_js_1 = require("../../operator/owner-event-board-refresh.js");
142
139
  const owner_event_loop_js_1 = require("../../operator/owner-event-loop.js");
140
+ const board_pipeline_render_js_1 = require("../../operator/board-pipeline-render.js");
143
141
  const owner_event_prompt_js_1 = require("../../operator/owner-event-prompt.js");
144
142
  const owner_event_policy_js_1 = require("../../operator/owner-event-policy.js");
145
143
  const leg_cadence_js_1 = require("../../operator/leg-cadence.js");
@@ -156,7 +154,6 @@ const principalRegistryLogger = new DebugLogger('PrincipalRegistry');
156
154
  function workOrderActivityDetails(event) {
157
155
  return event.briefHash ? { brief_hash: event.briefHash } : undefined;
158
156
  }
159
- const ownerWorkOrderLogger = new DebugLogger('OwnerWorkOrder');
160
157
  const DISABLED_PRIVATE_CONNECTOR_POLICY = (0, private_connector_policy_js_1.resolvePrivateConnectorPolicy)({
161
158
  ok: true,
162
159
  config: {},
@@ -393,12 +390,18 @@ function causeEventIdsFromPayload(payload) {
393
390
  }
394
391
  function workOrderEnvelopeScope(input) {
395
392
  const isTemporal = input.workKind === 'temporal';
396
- const surface = (0, private_connector_policy_js_1.resolveWorkOrderPrivateSurface)(input.workKind);
393
+ // One principal for every turn; what differs per kind is WHICH connectors the run
394
+ // may read, and that is data: a recheck reads its task's connector or nothing, a
395
+ // wiki turn reads no PRIVATE connector (personal channels never feed public pages),
396
+ // the board and curation turns keep the lane's configured connectors.
397
+ const surface = exports.ONE_AGENT_TURN_POLICY.roleName;
397
398
  const candidateConnectors = isTemporal
398
399
  ? input.temporalBinding
399
400
  ? [input.temporalBinding.connector]
400
401
  : []
401
- : input.laneConnectors;
402
+ : input.workKind === 'wiki'
403
+ ? input.laneConnectors.filter((name) => !input.privateConnectorPolicy.enabledPrivateConnectors.includes(name))
404
+ : input.laneConnectors;
402
405
  return {
403
406
  project_refs: [{ kind: 'project', id: input.projectId }],
404
407
  // A temporal run reads its task's connector or nothing. Every other lane keeps the
@@ -426,6 +429,11 @@ function workOrderEnvelopeScope(input) {
426
429
  },
427
430
  ]
428
431
  : []),
432
+ // Same rule for a board reconcile: the run is bound to the channel whose delta it
433
+ // judges, and connector memories are scoped by the canonical channel key.
434
+ ...(input.workKind === 'board' && input.reconcileChannelKey
435
+ ? [{ kind: 'channel', id: input.reconcileChannelKey }]
436
+ : []),
429
437
  ],
430
438
  allowed_destinations: [],
431
439
  };
@@ -453,48 +461,115 @@ function buildCodeActRole(policy) {
453
461
  sensitiveAccess: false,
454
462
  };
455
463
  }
456
- // Stage-2 workers are short-lived operator jobs, not standing multi-agent
457
- // personas. Their permissions must therefore be complete on a default install
458
- // and must not vary with optional legacy agent configuration.
459
- exports.WORKORDER_TOOL_POLICIES = {
460
- board: {
461
- roleName: 'workorder-board',
462
- allowedTools: [
463
- 'agent_notices',
464
- 'changes_read',
465
- 'context_compile',
466
- 'contract_no_update',
467
- 'mama_search',
468
- 'report_publish',
469
- 'task_create',
470
- 'task_external_correlation',
471
- 'task_external_bind',
472
- 'task_lifecycle_reconcile',
473
- 'task_list',
474
- 'task_update',
475
- 'trello_card',
476
- 'trello_kanban',
477
- 'trello_search',
478
- ],
479
- },
480
- wiki: {
481
- roleName: 'workorder-wiki',
482
- allowedTools: ['agent_notices', 'context_compile', 'mama_search', 'obsidian', 'wiki_publish'],
483
- },
484
- 'memory-curation': {
485
- roleName: 'workorder-memory-curation',
486
- allowedTools: ['agent_notices', 'mama_save', 'mama_search'],
487
- },
488
- temporal: {
489
- roleName: 'workorder-temporal',
490
- allowedTools: [
491
- 'agent_notices',
492
- 'context_compile',
493
- 'schedule_upcoming',
494
- 'task_list',
495
- 'task_temporal_reconcile',
496
- ],
497
- },
464
+ /**
465
+ * One MAMA: every scheduled turn runs as the SAME principal the owner console and the
466
+ * event turn use. `roleName` is not a label - resolvePrivatePrincipalSurface switches on
467
+ * it and falls through to 'multi-agent-generic' for anything it does not know, which
468
+ * splits the advertised catalog from the execution envelope.
469
+ */
470
+ exports.ONE_AGENT_TURN_POLICY = { roleName: 'owner_console' };
471
+ /** Owner-authored chat only: administration never runs unattended. */
472
+ exports.ADMINISTRATION_TOOLS = new Set([
473
+ 'member_register',
474
+ 'member_suspend',
475
+ 'member_offboard',
476
+ 'member_scope_grant',
477
+ 'member_scope_revoke',
478
+ 'console_brief_update',
479
+ 'report_request',
480
+ ]);
481
+ /**
482
+ * Per-turn-kind artifact tools, projected by the HOST. These exist only for scheduled
483
+ * turns and are deliberately absent from the chat grant: a chat turn never publishes a
484
+ * board slot or a wiki page and never files a temporal receipt. They are the tools the
485
+ * turn-kind section instructs the turn to use, so grant and instruction cannot drift.
486
+ */
487
+ exports.TURN_KIND_REQUIRED_TOOLS = {
488
+ board: [
489
+ 'agent_notices',
490
+ 'contract_no_update',
491
+ 'report_publish',
492
+ 'task_external_correlation',
493
+ 'task_external_bind',
494
+ 'task_lifecycle_reconcile',
495
+ ],
496
+ wiki: ['agent_notices', 'contract_no_update', 'wiki_publish'],
497
+ 'memory-curation': ['agent_notices', 'contract_no_update'],
498
+ temporal: ['agent_notices', 'contract_no_update', 'task_temporal_reconcile'],
499
+ };
500
+ /**
501
+ * Blocked on EVERY unattended turn. Deliverable, image and Drive tools belong to the
502
+ * owner conversation, and the tier-2 Code-Act projection refuses them anyway - a tool
503
+ * that is advertised but never injected is a hallucinated-call generator. Member data and
504
+ * workspace file reads are owner-conversation material with no use in a scheduled turn.
505
+ */
506
+ exports.SCHEDULED_TURN_BLOCKED_TOOLS = new Set([
507
+ // workspace file reads are an owner-conversation tool; no turn section instructs one
508
+ 'Read',
509
+ // every outbound channel, not only Telegram: the grant is derived from the owner's
510
+ // (editable) role config, so a send tool added there must still never run unattended
511
+ 'telegram_send',
512
+ 'discord_send',
513
+ 'slack_send',
514
+ 'webchat_send',
515
+ 'drive_upload',
516
+ 'drive_list_drives',
517
+ 'drive_browse',
518
+ 'drive_find_folder',
519
+ 'drive_download',
520
+ 'ocr_image',
521
+ 'create_fb_overlay',
522
+ 'translate_conti',
523
+ 'drive_translate_conti',
524
+ 'member_candidates',
525
+ 'member_list',
526
+ 'member_scope_list',
527
+ ]);
528
+ /**
529
+ * Per-turn-kind blocked lists, projected by the HOST. One principal and one brief do
530
+ * not mean one grant for every trigger: an unattended board or wiki turn has no owner
531
+ * in the loop, so it never holds a send, an upload, or a mutation outside its own
532
+ * artifact. Each entry names the artifact it protects.
533
+ */
534
+ exports.TURN_KIND_BLOCKED_TOOLS = {
535
+ // board writes judgment slots and task lifecycle (reconcile mode creates rows for new
536
+ // items, which the action verifier's obligated set expects); it does not touch memory
537
+ // or the vault (obsidian is the wiki lane's write path)
538
+ board: new Set([
539
+ 'wiki_publish',
540
+ 'obsidian',
541
+ 'mama_save',
542
+ 'mama_update',
543
+ 'task_temporal_reconcile',
544
+ ]),
545
+ // wiki writes pages; the ledger, the board and memory are not its artifact
546
+ wiki: new Set([
547
+ 'report_publish',
548
+ 'mama_save',
549
+ 'mama_update',
550
+ 'task_create',
551
+ 'task_update',
552
+ 'task_temporal_reconcile',
553
+ ]),
554
+ // curation writes memory; nothing else
555
+ 'memory-curation': new Set([
556
+ 'report_publish',
557
+ 'wiki_publish',
558
+ 'task_create',
559
+ 'task_update',
560
+ 'task_temporal_reconcile',
561
+ 'obsidian',
562
+ ]),
563
+ // recheck resolves ONE task through task_temporal_reconcile; generic mutation is out
564
+ temporal: new Set([
565
+ 'report_publish',
566
+ 'wiki_publish',
567
+ 'mama_save',
568
+ 'mama_update',
569
+ 'task_create',
570
+ 'task_update',
571
+ 'obsidian',
572
+ ]),
498
573
  };
499
574
  /**
500
575
  * Packet-only reports receive no gateway tools. The host compiles current bounded authorities
@@ -563,27 +638,42 @@ function buildOperatorReportAgentPolicy(model, backend, privateConnectorPolicy)
563
638
  briefProjectionPolicy: requiredPrivatePolicy,
564
639
  };
565
640
  }
566
- function buildWorkOrderAgentPolicy(kind, model, backend, privateConnectorPolicy, rawConnectorScope) {
641
+ /** Outbound effect by name: sends to a channel or uploads to a shared store. */
642
+ function isOutboundToolName(tool) {
643
+ return /(^|_)(send|upload)(_|$)/.test(tool);
644
+ }
645
+ function buildTurnAgentPolicy(kind, model, backend, privateConnectorPolicy, rawConnectorScope, ownerRole = types_js_1.DEFAULT_ROLES.definitions.owner_console) {
567
646
  const requiredPrivatePolicy = requirePrivateConnectorPolicy(privateConnectorPolicy);
568
647
  if (!rawConnectorScope) {
569
648
  throw new Error('rawConnectorScope is required');
570
649
  }
571
- const policy = exports.WORKORDER_TOOL_POLICIES[kind];
572
- if (!policy) {
573
- throw new Error(`Missing built-in workorder tool policy for '${kind}'`);
650
+ const kindBlocked = exports.TURN_KIND_BLOCKED_TOOLS[kind];
651
+ if (!kindBlocked) {
652
+ throw new Error(`Missing turn-kind blocked list for '${kind}'`);
574
653
  }
575
- const surface = (0, private_connector_policy_js_1.resolveWorkOrderPrivateSurface)(kind);
654
+ // Same downgrade the event turn applies: a run with no private binding advertises
655
+ // no private connector tools and gets the disabled brief projection.
576
656
  const scopedSurface = requiredPrivatePolicy.enabledPrivateConnectors.some((name) => rawConnectorScope.includes(name))
577
- ? surface
657
+ ? exports.ONE_AGENT_TURN_POLICY.roleName
578
658
  : 'multi-agent-generic';
579
- const projectedRole = buildProjectedLaneRole(scopedSurface, policy.allowedTools, requiredPrivatePolicy);
580
- const blockedTools = [...(projectedRole.blockedTools ?? [])];
581
- const innerTools = uniqueToolList(projectedRole.allowedTools);
659
+ const blockedSet = new Set([
660
+ ...exports.ADMINISTRATION_TOOLS,
661
+ ...exports.SCHEDULED_TURN_BLOCKED_TOOLS,
662
+ ...kindBlocked,
663
+ ...(ownerRole.blockedTools ?? []),
664
+ // Shape rule on top of the named lists: the role config is owner-editable, so a
665
+ // send or upload tool that appears there tomorrow is still never unattended.
666
+ ...ownerRole.allowedTools.filter((tool) => isOutboundToolName(tool)),
667
+ ]);
668
+ const projectedRole = buildProjectedLaneRole(scopedSurface, uniqueToolList([...ownerRole.allowedTools, ...exports.TURN_KIND_REQUIRED_TOOLS[kind]]).filter((tool) => !blockedSet.has(tool)), requiredPrivatePolicy);
669
+ const blockedTools = [...new Set([...(projectedRole.blockedTools ?? []), ...blockedSet])];
670
+ // code_act is the transport, re-added at the front below; it is never an inner tool.
671
+ const innerTools = uniqueToolList(projectedRole.allowedTools).filter((tool) => !blockedSet.has(tool) && tool !== 'code_act');
582
672
  const allowedTools = uniqueToolList(['code_act', ...innerTools]);
583
673
  const agentContext = {
584
674
  source: 'operator',
585
675
  platform: 'cli',
586
- roleName: policy.roleName,
676
+ roleName: exports.ONE_AGENT_TURN_POLICY.roleName,
587
677
  role: { ...projectedRole, allowedTools, blockedTools, model },
588
678
  session: {
589
679
  sessionId: `operator:worker:${kind}`,
@@ -655,104 +745,27 @@ function buildSystemAgentProcessDefaults(config) {
655
745
  dangerouslySkipPermissions: config.multi_agent?.dangerouslySkipPermissions ?? true,
656
746
  };
657
747
  }
658
- const OWNER_BOARD_FULL_REPAIR_CHANNEL = 'host:owner-workorder:board-full';
659
- function buildOwnerEventBoardRefreshRuntime(operatorDb, taskLedger, now = Date.now) {
660
- const ownerEventBoardRefreshLedger = new owner_event_board_refresh_js_1.OwnerEventBoardRefreshLedger(operatorDb, taskLedger, now);
661
- const initialGeneration = (0, owner_event_board_refresh_js_1.resolveInitialBoardRepairGeneration)(now(), ownerEventBoardRefreshLedger.maxPendingGeneration());
662
- return {
663
- ownerEventBoardRefreshLedger,
664
- boardRefreshGate: new board_refresh_gate_js_1.BoardRefreshGate({ initialGeneration }),
665
- };
666
- }
667
748
  function resolveOwnerEventTerminalReceipt(batch, deps) {
668
- const confirmedEffects = deps.ownerEventEffectLedger.confirmedKinds(batch.id);
669
- if (confirmedEffects.length > 0) {
670
- return { status: 'acted', tools: confirmedEffects };
671
- }
672
- const acceptedWorkOrder = deps.ownerEventBoardRefreshLedger.findAcceptance(batch.id) ??
673
- deps.taskLedger.findWorkOrderByOccurrence('board', (0, workorder_publishers_js_1.boardBatchKey)(batch.eventIds)) ??
674
- deps.taskLedger.findWorkOrderByOccurrence('wiki', (0, workorder_publishers_js_1.ownerEventWorkOrderKey)('wiki', batch.eventIds)) ??
675
- deps.taskLedger.findWorkOrderByOccurrence('memory-curation', (0, workorder_publishers_js_1.ownerEventWorkOrderKey)('memory-curation', batch.eventIds));
676
- if (acceptedWorkOrder) {
677
- return { status: 'delegated', tools: ['workorder_request'] };
749
+ // Crash-recovery mirror of classifyOwnerEventOutcome. A ledger write that
750
+ // named this batch's events as its cause (evidence_effects) or a confirmed
751
+ // deliverable (drive_upload) is a durable outcome even if the run died after
752
+ // it - without this, a task created before a transport error is created again
753
+ // on retry. A confirmed telegram_send alone is not a receipt, and the effect
754
+ // ledger's (batch, action_key) idempotency makes that retry safe from a second
755
+ // send. A persisted Board acceptance is no longer a receipt.
756
+ const ledgerEffects = deps.taskLedger.effectsCausedBy(batch.eventIds, batch.createdAt);
757
+ const confirmedEffects = deps.ownerEventEffectLedger
758
+ .confirmedKinds(batch.id)
759
+ .filter((kind) => kind !== 'telegram_send');
760
+ const durable = [...new Set([...ledgerEffects, ...confirmedEffects])];
761
+ if (durable.length > 0) {
762
+ return { status: 'acted', tools: durable, ownerDecisionRequested: false };
678
763
  }
679
764
  if (deps.taskLedger.maxNoUpdateId(`owner-event:${batch.id}`) > 0) {
680
765
  return { status: 'no_update', tools: [] };
681
766
  }
682
767
  return null;
683
768
  }
684
- /** Build the enqueue-and-ack handler shared by owner chat and owner-event turns. */
685
- function buildOwnerWorkOrderRequestHandler(deps) {
686
- const now = deps.now ?? Date.now;
687
- const log = deps.log ?? ((line) => ownerWorkOrderLogger.info(line));
688
- const logError = deps.logError ?? ((line, detail) => ownerWorkOrderLogger.error(line, detail));
689
- return (kind, origin = { kind: 'owner_manual' }) => {
690
- try {
691
- const requestedAt = now();
692
- let idempotencyKey;
693
- let payload;
694
- if (kind === 'board') {
695
- if (!deps.boardRefreshGate) {
696
- throw new Error('Board refresh gate is unavailable');
697
- }
698
- if (origin.kind === 'owner_event') {
699
- if (!deps.ownerEventBoardRefreshLedger) {
700
- throw new Error('Owner-event Board refresh ledger is unavailable');
701
- }
702
- const existing = deps.ownerEventBoardRefreshLedger.findAcceptance(origin.batchId);
703
- if (existing) {
704
- log(`[stage2] owner workorder already accepted: board#${existing.workOrderId}`);
705
- return { accepted: true };
706
- }
707
- deps.boardRefreshGate.markChannelDirty(OWNER_BOARD_FULL_REPAIR_CHANNEL);
708
- const accepted = deps.ownerEventBoardRefreshLedger.accept({
709
- batchId: origin.batchId,
710
- eventIds: origin.eventIds,
711
- repair: deps.boardRefreshGate.captureFullRepair(),
712
- });
713
- log(`[stage2] owner workorder accepted: board#${accepted.workOrderId}`);
714
- return { accepted: true };
715
- }
716
- // TG-06: host dirt is recorded before validation or enqueue can fail.
717
- // The fixed synthetic channel never trusts model/event identifiers.
718
- deps.boardRefreshGate.markChannelDirty(OWNER_BOARD_FULL_REPAIR_CHANNEL);
719
- const repair = deps.boardRefreshGate.captureFullRepair();
720
- idempotencyKey = (0, workorder_publishers_js_1.boardManualKey)(requestedAt);
721
- payload = { mode: 'full', force: true, ...repair };
722
- }
723
- else if (kind === 'wiki') {
724
- if (origin.kind === 'owner_event') {
725
- idempotencyKey = (0, workorder_publishers_js_1.ownerEventWorkOrderKey)('wiki', origin.eventIds);
726
- payload = { batchId: idempotencyKey, events: [...origin.eventIds] };
727
- }
728
- else {
729
- idempotencyKey = (0, workorder_publishers_js_1.wikiBatchKey)('manual', requestedAt);
730
- payload = { batchId: `${requestedAt}-manual`, events: ['manual'] };
731
- }
732
- }
733
- else {
734
- idempotencyKey =
735
- origin.kind === 'owner_event'
736
- ? (0, workorder_publishers_js_1.ownerEventWorkOrderKey)('memory-curation', origin.eventIds)
737
- : (0, workorder_publishers_js_1.promotionManualKey)(requestedAt);
738
- payload = { scheduledAt: new Date(requestedAt).toISOString() };
739
- }
740
- (0, workorder_publishers_js_1.validateWorkOrderPayload)(kind, payload);
741
- const wo = deps.taskLedger.enqueueWorkOrder({
742
- workKind: kind,
743
- idempotencyKey,
744
- input: payload,
745
- priority: 'high',
746
- });
747
- log(`[stage2] owner workorder enqueued: ${kind}#${wo.id}`);
748
- return { accepted: true };
749
- }
750
- catch (err) {
751
- logError(`[stage2] owner workorder enqueue failed (${kind}):`, err instanceof Error ? err.message : err);
752
- return { accepted: false, reason: 'enqueue-failed' };
753
- }
754
- };
755
- }
756
769
  /**
757
770
  * Execute start command
758
771
  */
@@ -864,8 +877,10 @@ async function runAgentLoop(config, options = {}) {
864
877
  const temporalStartup = (0, temporal_runtime_js_1.preflightTemporalStartup)(process.env);
865
878
  const runtimeBackend = requireRuntimeBackend(config.agent.backend);
866
879
  const { connectorConfigLoadResult, privateConnectorPolicy } = (0, connector_bootstrap_js_1.resolveRuntimeConnectorBootstrap)((0, config_loader_js_1.loadConnectorConfig)());
880
+ // ONE owner role for every model turn: chat, event, and scheduled.
881
+ const ownerRole = config.roles?.definitions.owner_console ?? types_js_1.DEFAULT_ROLES.definitions.owner_console;
867
882
  const temporalPolicy = temporalStartup.temporalFlag === 'on'
868
- ? buildWorkOrderAgentPolicy('temporal', config.agent.model, runtimeBackend, privateConnectorPolicy, [])
883
+ ? buildTurnAgentPolicy('temporal', config.agent.model, runtimeBackend, privateConnectorPolicy, [], ownerRole)
869
884
  : null;
870
885
  const temporalEffectiveTools = temporalPolicy
871
886
  ? (0, tool_policy_js_1.projectCodeActToolPolicy)({
@@ -1316,7 +1331,9 @@ async function runAgentLoop(config, options = {}) {
1316
1331
  // delta reconcile scheduling; exact owner-event Board acceptance, boot
1317
1332
  // recovery, manual refresh, and full verification share this runtime in all
1318
1333
  // configurations.
1319
- const { ownerEventBoardRefreshLedger, boardRefreshGate } = buildOwnerEventBoardRefreshRuntime(operatorDb, taskLedger);
1334
+ // Host-owned Board repair gate. Generations are wall time: with the owner-event
1335
+ // intent ledger gone there is no persisted pending generation to seed above.
1336
+ const boardRefreshGate = new board_refresh_gate_js_1.BoardRefreshGate({ initialGeneration: Date.now() });
1320
1337
  const ownerEventEffectLedger = new owner_event_effects_js_1.OwnerEventEffectLedger(operatorDb);
1321
1338
  toolExecutor.setOwnerEventEffectLedger(ownerEventEffectLedger);
1322
1339
  let stopOwnerEventRuntime = null;
@@ -1366,6 +1383,9 @@ async function runAgentLoop(config, options = {}) {
1366
1383
  resolveWorkerRequestTimeoutMs();
1367
1384
  let workOrderConsumer = null;
1368
1385
  const boardRepairNudge = { current: null };
1386
+ const pipelineSlotPublisher = {
1387
+ current: null,
1388
+ };
1369
1389
  let temporalRuntime = null;
1370
1390
  gateways.push({
1371
1391
  stop: async () => {
@@ -1388,10 +1408,8 @@ async function runAgentLoop(config, options = {}) {
1388
1408
  });
1389
1409
  {
1390
1410
  const { WorkOrderConsumer } = await import('../../operator/workorder-consumer.js');
1391
- const { loadBrief, ensureBriefs } = await import('../../operator/briefs.js');
1392
- // Seed missing default briefs (user edits win) BEFORE the consumer exists -
1411
+ // Seed the ONE operating brief (user edits win) BEFORE the consumer exists -
1393
1412
  // a normal install must never hit the brief-missing fail path.
1394
- ensureBriefs();
1395
1413
  (0, console_brief_js_1.ensureConsoleBrief)();
1396
1414
  const { logActivity: logWorkOrderActivity } = await import('../../db/agent-store.js');
1397
1415
  // Ops alarm sink (plan D4/E1/G8): constructed OUTSIDE any trigger-loop
@@ -1423,7 +1441,20 @@ async function runAgentLoop(config, options = {}) {
1423
1441
  workOrderConsumer = new WorkOrderConsumer({
1424
1442
  ledger: taskLedger,
1425
1443
  runner: workerRunner,
1426
- loadBrief: (kind) => loadBrief(kind),
1444
+ loadOwnerBrief: () => (0, console_brief_js_1.loadConsoleBrief)(),
1445
+ // Host-rendered pipeline: the ledger's own deadline_priority page, published through
1446
+ // the SAME report publisher report_publish uses (bound after the API routes exist).
1447
+ publishPipelineSlot: () => {
1448
+ const publish = pipelineSlotPublisher.current;
1449
+ if (!publish)
1450
+ throw new Error('pipeline slot publisher is not bound yet');
1451
+ const page = taskLedger.listPage({
1452
+ includeTerminal: false,
1453
+ order: 'deadline_priority',
1454
+ limit: board_pipeline_render_js_1.PIPELINE_SLOT_ROWS,
1455
+ });
1456
+ publish({ pipeline: (0, board_pipeline_render_js_1.renderPipelineSlot)(page.tasks, Date.now(), page.total) });
1457
+ },
1427
1458
  noticeOwner: (summary) => messageRouter.enqueueOperatorNotice(summary),
1428
1459
  opsAlarm,
1429
1460
  runOptionsFor: async (wo) => {
@@ -1439,11 +1470,17 @@ async function runAgentLoop(config, options = {}) {
1439
1470
  temporalBinding = temporalTaskBinding(taskLedger, temporalContext.taskId);
1440
1471
  }
1441
1472
  const projectId = (0, reactive_config_js_1.resolveReactiveProjectRoot)(config, process.env);
1473
+ const reconcileChannelKey = wo.workKind === 'board' &&
1474
+ wo.payload.mode === 'reconcile' &&
1475
+ typeof wo.payload.channelKey === 'string'
1476
+ ? wo.payload.channelKey
1477
+ : null;
1442
1478
  const workOrderScope = workOrderEnvelopeScope({
1443
1479
  workKind: wo.workKind,
1444
1480
  projectId,
1445
1481
  laneConnectors: codeActRawConnectors,
1446
1482
  temporalBinding,
1483
+ reconcileChannelKey,
1447
1484
  privateConnectorPolicy,
1448
1485
  });
1449
1486
  // Worker prompt selects the provider's supported tool path: Claude's
@@ -1451,7 +1488,7 @@ async function runAgentLoop(config, options = {}) {
1451
1488
  // spawn-default code-act path, where per-run envelope/capture overrides
1452
1489
  // cannot reach (shadow-gate §8.2).
1453
1490
  const { buildWorkerSystemPrompt } = await import('../../operator/worker-run.js');
1454
- const workOrderPolicy = buildWorkOrderAgentPolicy(wo.workKind, config.agent.model, runtimeBackend, privateConnectorPolicy, workOrderScope.raw_connectors);
1491
+ const workOrderPolicy = buildTurnAgentPolicy(wo.workKind, config.agent.model, runtimeBackend, privateConnectorPolicy, workOrderScope.raw_connectors, ownerRole);
1455
1492
  const runOptions = attachWorkOrderAttemptContext({
1456
1493
  systemPrompt: buildWorkerSystemPrompt(workOrderPolicy.gatewayToolsPrompt, runtimeBackend, wo.workKind),
1457
1494
  agentContext: workOrderPolicy.agentContext,
@@ -1489,7 +1526,7 @@ async function runAgentLoop(config, options = {}) {
1489
1526
  if (envelopeBootstrap.envelopeAuthority && envelopeBootstrap.metadata.issuance !== 'off') {
1490
1527
  const wallSeconds = Math.min(Math.max(Number(process.env.MAMA_REPORT_WALL_SECONDS) || 900, 60), 1800);
1491
1528
  runOptions.envelope = await envelopeBootstrap.envelopeAuthority.buildAndPersist({
1492
- agent_id: `workorder-${wo.workKind}`,
1529
+ agent_id: 'mama-owner',
1493
1530
  instance_id: (0, node_crypto_1.randomUUID)(),
1494
1531
  // 'watch' = daemon-internal issuing source (closed EnvelopeSource
1495
1532
  // union; enforcement authorizes on scope, never on source).
@@ -1511,7 +1548,7 @@ async function runAgentLoop(config, options = {}) {
1511
1548
  // keeps the operational trace queryable.
1512
1549
  try {
1513
1550
  logWorkOrderActivity(db, {
1514
- agent_id: `workorder-${event.workKind}`,
1551
+ agent_id: 'mama-owner',
1515
1552
  agent_version: 0,
1516
1553
  type: `workorder_${event.type}`,
1517
1554
  input_summary: `#${event.workOrderId}`,
@@ -1528,21 +1565,9 @@ async function runAgentLoop(config, options = {}) {
1528
1565
  catch {
1529
1566
  /* telemetry only */
1530
1567
  }
1531
- if (event.type === 'complete' &&
1532
- event.workKind === 'board' &&
1533
- ownerEventBoardRefreshLedger.consumePostTerminalFollowup(event.workOrderId)) {
1534
- boardRepairNudge.current?.();
1535
- }
1536
1568
  },
1537
1569
  });
1538
1570
  // (Consumer stop is folded into the operator-DB gateway above - ordering.)
1539
- // Owner-issued workorders (workorder_request tool): enqueue+ack only.
1540
- // Wired here - NOT inside any trigger-loop block (plan C11 class).
1541
- toolExecutor.setWorkOrderRequestHandler(buildOwnerWorkOrderRequestHandler({
1542
- taskLedger,
1543
- boardRefreshGate,
1544
- ownerEventBoardRefreshLedger,
1545
- }));
1546
1571
  }
1547
1572
  const temporalTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1548
1573
  const temporalAssembly = (0, temporal_init_js_1.assembleDaemonTemporalRuntime)({
@@ -1661,6 +1686,21 @@ async function runAgentLoop(config, options = {}) {
1661
1686
  issuance: envelopeBootstrap.metadata.issuance,
1662
1687
  hasAuthority: ownerEventEnvelopeAuthority !== undefined,
1663
1688
  });
1689
+ // Scope-independent packet readers, shared by the report leg and the event turn.
1690
+ const reportContextDeps = {
1691
+ listTaskPage: (input) => taskLedger.listPage(input),
1692
+ readClaims: (scope) => mamaCore.queryRelevantTruth({ query: '', scopes: scope.memoryScopes }),
1693
+ readTrello: async (scope) => {
1694
+ if (!scope.rawConnectors.includes('trello')) {
1695
+ throw new Error('Trello is outside the owner-report read scope');
1696
+ }
1697
+ return (0, query_tools_js_1.getTrelloKanban)({ maxCardsPerList: 100 });
1698
+ },
1699
+ buildProvenanceLookup: provenance_lookup_js_1.buildProvenanceLookup,
1700
+ correlate: external_correlation_js_1.correlateTasksWithExternalItems,
1701
+ readChanges: (_scope, input, nowMs) => (0, changes_projection_js_1.readChanges)(taskLedger, input, nowMs),
1702
+ now: Date.now,
1703
+ };
1664
1704
  const triggerLoop = new OperatorTriggerLoop({
1665
1705
  backend: runtimeBackend,
1666
1706
  delta: new ConnectorDeltaRepo(getAdapter(), (0, config_manager_js_1.expandPath)('~/.mama/operator/trigger-loop-cursors.json')),
@@ -1726,20 +1766,7 @@ async function runAgentLoop(config, options = {}) {
1726
1766
  ],
1727
1767
  };
1728
1768
  })(),
1729
- compileFullReportContext: async ({ readScope, windowEvidence, since }) => (0, report_context_js_1.compileOwnerReportContext)({ readScope, windowEvidence, since }, {
1730
- listTaskPage: (input) => taskLedger.listPage(input),
1731
- readClaims: (scope) => mamaCore.queryRelevantTruth({ query: '', scopes: scope.memoryScopes }),
1732
- readTrello: async (scope) => {
1733
- if (!scope.rawConnectors.includes('trello')) {
1734
- throw new Error('Trello is outside the owner-report read scope');
1735
- }
1736
- return (0, query_tools_js_1.getTrelloKanban)({ maxCardsPerList: 100 });
1737
- },
1738
- buildProvenanceLookup: provenance_lookup_js_1.buildProvenanceLookup,
1739
- correlate: external_correlation_js_1.correlateTasksWithExternalItems,
1740
- readChanges: (_scope, input, nowMs) => (0, changes_projection_js_1.readChanges)(taskLedger, input, nowMs),
1741
- now: Date.now,
1742
- }),
1769
+ compileFullReportContext: async ({ readScope, windowEvidence, since }) => (0, report_context_js_1.compileOwnerReportContext)({ readScope, windowEvidence, since }, reportContextDeps),
1743
1770
  // TG-06: composition supplies provenance immediately to the prepared
1744
1771
  // delivery; this callback receives that durable artifact only after send success.
1745
1772
  fullReportProvenance: () => lastReportProvenance,
@@ -1792,7 +1819,6 @@ async function runAgentLoop(config, options = {}) {
1792
1819
  // MAMA itself owns connector events. This is the same AgentLoop and
1793
1820
  // owner operating policy as the owner console, on a durable per-channel
1794
1821
  // background session. There is no separate Conductor persona.
1795
- const ownerRole = config.roles?.definitions.owner_console ?? types_js_1.DEFAULT_ROLES.definitions.owner_console;
1796
1822
  const ownerEventContext = (0, owner_event_policy_js_1.buildOwnerEventAgentContext)({
1797
1823
  backend: runtimeBackend,
1798
1824
  model: config.agent.model,
@@ -1800,8 +1826,24 @@ async function runAgentLoop(config, options = {}) {
1800
1826
  privateConnectorPolicy,
1801
1827
  });
1802
1828
  const ownerEventPromptEnhancer = new prompt_enhancer_js_1.PromptEnhancer();
1803
- const ownerEventIssueEnvelope = async (batch) => {
1829
+ // ONE expression for what an event turn may read: the envelope and the
1830
+ // packet are both built from it, so they can never disagree.
1831
+ const ownerEventReadScope = (batch) => {
1804
1832
  const projectId = (0, reactive_config_js_1.resolveReactiveProjectRoot)(config, process.env);
1833
+ return {
1834
+ projectRefs: [{ kind: 'project', id: projectId }],
1835
+ rawConnectors: [
1836
+ ...privateConnectorPolicy.projectRawConnectors('owner_console', codeActRawConnectors),
1837
+ ],
1838
+ memoryScopes: uniqueMemoryScopes((0, scope_context_js_1.deriveMemoryScopes)({
1839
+ source: 'owner-event',
1840
+ channelId: batch.channelKey,
1841
+ projectId,
1842
+ })),
1843
+ };
1844
+ };
1845
+ const ownerEventIssueEnvelope = async (batch) => {
1846
+ const readScope = ownerEventReadScope(batch);
1805
1847
  const wallSeconds = 300;
1806
1848
  return ownerEventEnvelopeAuthority.buildAndPersist({
1807
1849
  agent_id: 'mama-owner',
@@ -1810,15 +1852,9 @@ async function runAgentLoop(config, options = {}) {
1810
1852
  channel_id: batch.channelKey,
1811
1853
  trigger_context: { user_text: '<MAMA owner event turn>' },
1812
1854
  scope: {
1813
- project_refs: [{ kind: 'project', id: projectId }],
1814
- raw_connectors: [
1815
- ...privateConnectorPolicy.projectRawConnectors('owner_console', codeActRawConnectors),
1816
- ],
1817
- memory_scopes: uniqueMemoryScopes((0, scope_context_js_1.deriveMemoryScopes)({
1818
- source: 'owner-event',
1819
- channelId: batch.channelKey,
1820
- projectId,
1821
- })),
1855
+ project_refs: readScope.projectRefs,
1856
+ raw_connectors: readScope.rawConnectors,
1857
+ memory_scopes: readScope.memoryScopes,
1822
1858
  allowed_destinations: reportChatId
1823
1859
  ? [{ kind: 'telegram', id: reportChatId }]
1824
1860
  : [],
@@ -1832,8 +1868,15 @@ async function runAgentLoop(config, options = {}) {
1832
1868
  inbox: ownerEventInbox,
1833
1869
  runner: agentLoop,
1834
1870
  agentContext: ownerEventContext,
1835
- buildPrompt: async (batch) => (0, owner_event_prompt_js_1.buildOwnerEventPrompt)({
1871
+ compilePacket: async (batch) => (0, report_context_js_1.serializeOwnerReportContext)(await (0, report_context_js_1.compileChannelPacket)({
1872
+ readScope: ownerEventReadScope(batch),
1873
+ channelKey: batch.channelKey,
1874
+ eventIds: batch.eventIds,
1875
+ since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
1876
+ }, reportContextDeps)),
1877
+ buildPrompt: async (batch, packet) => (0, owner_event_prompt_js_1.buildOwnerEventPrompt)({
1836
1878
  batch,
1879
+ packet,
1837
1880
  ownerBrief: (0, console_brief_js_1.projectConsoleBriefForPrompt)((0, console_brief_js_1.loadConsoleBrief)(), privateConnectorPolicy),
1838
1881
  skillContent: await ownerEventPromptEnhancer.detectSkillMatch(batch.lines.join('\n')),
1839
1882
  ownerTelegramChatId: reportChatId,
@@ -1842,7 +1885,6 @@ async function runAgentLoop(config, options = {}) {
1842
1885
  getNoUpdateMaxId: (scope) => taskLedger.maxNoUpdateId(scope),
1843
1886
  getTerminalReceipt: (batch) => resolveOwnerEventTerminalReceipt(batch, {
1844
1887
  ownerEventEffectLedger,
1845
- ownerEventBoardRefreshLedger,
1846
1888
  taskLedger,
1847
1889
  }),
1848
1890
  recordTriggerOutcome: (triggerId, outcome) => triggerRegistry.recordOutcome(triggerId, outcome),
@@ -1926,15 +1968,18 @@ async function runAgentLoop(config, options = {}) {
1926
1968
  getAdapter,
1927
1969
  privateConnectorPolicy,
1928
1970
  rawConnectorScope: [
1929
- ...privateConnectorPolicy.projectRawConnectors('workorder-board', codeActRawConnectors),
1971
+ ...privateConnectorPolicy.projectRawConnectors(exports.ONE_AGENT_TURN_POLICY.roleName, codeActRawConnectors),
1930
1972
  ],
1931
1973
  sessionsDb: db,
1932
1974
  workOrderConsumer: workOrderConsumer ?? undefined,
1933
1975
  boardRefreshGate,
1934
- ownerEventBoardRefreshLedger,
1935
1976
  requestFullReport: () => triggerLoopFullReport.current?.() ?? { accepted: false, reason: 'unavailable' },
1936
1977
  });
1937
1978
  boardRepairNudge.current = apiRoutesHandle.requestBoardRepair;
1979
+ {
1980
+ const { createReportPublisher } = await import('../../api/report-handler.js');
1981
+ pipelineSlotPublisher.current = createReportPublisher(apiServer.reportStore, apiServer.reportSseClients);
1982
+ }
1938
1983
  gateways.push({ stop: async () => apiRoutesHandle.stop() });
1939
1984
  // ── Stage-2 boot pass (plan S2-T3): runtime assembly registered hooks;
1940
1985
  // recovery/cleanup run after routes are ready, then the consumer starts.