@jungjaehoon/mama-os 0.48.4 → 0.49.1

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 (166) hide show
  1. package/dist/agent/agent-loop.d.ts +6 -7
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +106 -80
  4. package/dist/agent/agent-loop.js.map +1 -1
  5. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  6. package/dist/agent/code-act/host-bridge.js +7 -9
  7. package/dist/agent/code-act/host-bridge.js.map +1 -1
  8. package/dist/agent/codex-home.d.ts.map +1 -1
  9. package/dist/agent/codex-home.js +3 -1
  10. package/dist/agent/codex-home.js.map +1 -1
  11. package/dist/agent/gateway-tool-executor.d.ts +0 -16
  12. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  13. package/dist/agent/gateway-tool-executor.js +0 -51
  14. package/dist/agent/gateway-tool-executor.js.map +1 -1
  15. package/dist/agent/gateway-tools.md +1 -2
  16. package/dist/agent/os-agent-capabilities.md +14 -34
  17. package/dist/agent/role-manager.d.ts.map +1 -1
  18. package/dist/agent/role-manager.js +6 -3
  19. package/dist/agent/role-manager.js.map +1 -1
  20. package/dist/agent/tool-registry.d.ts.map +1 -1
  21. package/dist/agent/tool-registry.js +27 -8
  22. package/dist/agent/tool-registry.js.map +1 -1
  23. package/dist/agent/types.d.ts +20 -6
  24. package/dist/agent/types.d.ts.map +1 -1
  25. package/dist/agent/types.js.map +1 -1
  26. package/dist/api/graph-api-types.d.ts +0 -18
  27. package/dist/api/graph-api-types.d.ts.map +1 -1
  28. package/dist/api/graph-api.d.ts.map +1 -1
  29. package/dist/api/graph-api.js +9 -81
  30. package/dist/api/graph-api.js.map +1 -1
  31. package/dist/cli/commands/start.d.ts +7 -16
  32. package/dist/cli/commands/start.d.ts.map +1 -1
  33. package/dist/cli/commands/start.js +188 -189
  34. package/dist/cli/commands/start.js.map +1 -1
  35. package/dist/cli/config/types.d.ts.map +1 -1
  36. package/dist/cli/config/types.js +11 -4
  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 +31 -11
  40. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  41. package/dist/cli/runtime/api-routes-init.d.ts +0 -2
  42. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  43. package/dist/cli/runtime/api-routes-init.js +9 -95
  44. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  45. package/dist/cli/runtime/api-server-init.d.ts +2 -2
  46. package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
  47. package/dist/cli/runtime/api-server-init.js +2 -2
  48. package/dist/cli/runtime/api-server-init.js.map +1 -1
  49. package/dist/cli/runtime/scheduler-init.d.ts +3 -4
  50. package/dist/cli/runtime/scheduler-init.d.ts.map +1 -1
  51. package/dist/cli/runtime/scheduler-init.js +6 -16
  52. package/dist/cli/runtime/scheduler-init.js.map +1 -1
  53. package/dist/cli/runtime/shutdown.d.ts +0 -1
  54. package/dist/cli/runtime/shutdown.d.ts.map +1 -1
  55. package/dist/cli/runtime/shutdown.js +1 -1
  56. package/dist/cli/runtime/shutdown.js.map +1 -1
  57. package/dist/cli/runtime/types.d.ts +0 -5
  58. package/dist/cli/runtime/types.d.ts.map +1 -1
  59. package/dist/concurrency/lane-manager.d.ts +1 -1
  60. package/dist/concurrency/lane-manager.d.ts.map +1 -1
  61. package/dist/concurrency/lane-manager.js +4 -2
  62. package/dist/concurrency/lane-manager.js.map +1 -1
  63. package/dist/concurrency/types.d.ts +4 -0
  64. package/dist/concurrency/types.d.ts.map +1 -1
  65. package/dist/concurrency/types.js.map +1 -1
  66. package/dist/envelope/types.d.ts +1 -1
  67. package/dist/envelope/types.d.ts.map +1 -1
  68. package/dist/envelope/types.js.map +1 -1
  69. package/dist/gateways/message-router.d.ts +15 -71
  70. package/dist/gateways/message-router.d.ts.map +1 -1
  71. package/dist/gateways/message-router.js +124 -493
  72. package/dist/gateways/message-router.js.map +1 -1
  73. package/dist/gateways/session-store.d.ts +3 -23
  74. package/dist/gateways/session-store.d.ts.map +1 -1
  75. package/dist/gateways/session-store.js +2 -64
  76. package/dist/gateways/session-store.js.map +1 -1
  77. package/dist/gateways/telegram-report-context-store.d.ts +4 -66
  78. package/dist/gateways/telegram-report-context-store.d.ts.map +1 -1
  79. package/dist/gateways/telegram-report-context-store.js +11 -235
  80. package/dist/gateways/telegram-report-context-store.js.map +1 -1
  81. package/dist/gateways/turn-contract.d.ts +7 -3
  82. package/dist/gateways/turn-contract.d.ts.map +1 -1
  83. package/dist/operator/operator-trigger-loop.d.ts +1 -12
  84. package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
  85. package/dist/operator/operator-trigger-loop.js +6 -40
  86. package/dist/operator/operator-trigger-loop.js.map +1 -1
  87. package/dist/operator/owner-event-loop.d.ts +9 -8
  88. package/dist/operator/owner-event-loop.d.ts.map +1 -1
  89. package/dist/operator/owner-event-loop.js +8 -22
  90. package/dist/operator/owner-event-loop.js.map +1 -1
  91. package/dist/operator/owner-event-policy.d.ts.map +1 -1
  92. package/dist/operator/owner-event-policy.js +0 -2
  93. package/dist/operator/owner-event-policy.js.map +1 -1
  94. package/dist/operator/owner-event-prompt.d.ts +0 -2
  95. package/dist/operator/owner-event-prompt.d.ts.map +1 -1
  96. package/dist/operator/owner-event-prompt.js +1 -8
  97. package/dist/operator/owner-event-prompt.js.map +1 -1
  98. package/dist/operator/owner-runtime-journal.d.ts +21 -0
  99. package/dist/operator/owner-runtime-journal.d.ts.map +1 -0
  100. package/dist/operator/owner-runtime-journal.js +131 -0
  101. package/dist/operator/owner-runtime-journal.js.map +1 -0
  102. package/dist/operator/owner-runtime.d.ts +32 -0
  103. package/dist/operator/owner-runtime.d.ts.map +1 -0
  104. package/dist/operator/owner-runtime.js +31 -0
  105. package/dist/operator/owner-runtime.js.map +1 -0
  106. package/dist/operator/pending-report-store.d.ts +2 -2
  107. package/dist/operator/pending-report-store.d.ts.map +1 -1
  108. package/dist/operator/pending-report-store.js +4 -1
  109. package/dist/operator/pending-report-store.js.map +1 -1
  110. package/dist/operator/report-run.d.ts +7 -10
  111. package/dist/operator/report-run.d.ts.map +1 -1
  112. package/dist/operator/report-run.js +5 -26
  113. package/dist/operator/report-run.js.map +1 -1
  114. package/dist/operator/situation-report.d.ts +25 -6
  115. package/dist/operator/situation-report.d.ts.map +1 -1
  116. package/dist/operator/situation-report.js +28 -37
  117. package/dist/operator/situation-report.js.map +1 -1
  118. package/dist/operator/task-ledger.d.ts +8 -1
  119. package/dist/operator/task-ledger.d.ts.map +1 -1
  120. package/dist/operator/task-ledger.js +51 -16
  121. package/dist/operator/task-ledger.js.map +1 -1
  122. package/dist/operator/task-list-views.d.ts.map +1 -1
  123. package/dist/operator/task-list-views.js +45 -24
  124. package/dist/operator/task-list-views.js.map +1 -1
  125. package/dist/operator/task-temporal.d.ts +3 -0
  126. package/dist/operator/task-temporal.d.ts.map +1 -1
  127. package/dist/operator/task-temporal.js +12 -0
  128. package/dist/operator/task-temporal.js.map +1 -1
  129. package/dist/operator/trigger-author.d.ts +0 -45
  130. package/dist/operator/trigger-author.d.ts.map +1 -1
  131. package/dist/operator/trigger-author.js +0 -122
  132. package/dist/operator/trigger-author.js.map +1 -1
  133. package/dist/operator/worker-run.d.ts +9 -23
  134. package/dist/operator/worker-run.d.ts.map +1 -1
  135. package/dist/operator/worker-run.js +18 -74
  136. package/dist/operator/worker-run.js.map +1 -1
  137. package/dist/operator/workorder-consumer.d.ts.map +1 -1
  138. package/dist/operator/workorder-consumer.js +7 -0
  139. package/dist/operator/workorder-consumer.js.map +1 -1
  140. package/dist/scheduler/cron-worker.d.ts +0 -3
  141. package/dist/scheduler/cron-worker.d.ts.map +1 -1
  142. package/dist/scheduler/cron-worker.js +2 -13
  143. package/dist/scheduler/cron-worker.js.map +1 -1
  144. package/dist/scheduler/heartbeat.d.ts +3 -1
  145. package/dist/scheduler/heartbeat.d.ts.map +1 -1
  146. package/dist/scheduler/heartbeat.js +13 -4
  147. package/dist/scheduler/heartbeat.js.map +1 -1
  148. package/dist/validation/types.d.ts +1 -1
  149. package/dist/validation/types.js +1 -1
  150. package/package.json +1 -1
  151. package/dist/cli/runtime/memory-agent-init.d.ts +0 -37
  152. package/dist/cli/runtime/memory-agent-init.d.ts.map +0 -1
  153. package/dist/cli/runtime/memory-agent-init.js +0 -202
  154. package/dist/cli/runtime/memory-agent-init.js.map +0 -1
  155. package/dist/gateways/owner-report-inbox.d.ts +0 -40
  156. package/dist/gateways/owner-report-inbox.d.ts.map +0 -1
  157. package/dist/gateways/owner-report-inbox.js +0 -128
  158. package/dist/gateways/owner-report-inbox.js.map +0 -1
  159. package/dist/multi-agent/memory-agent-persona.d.ts +0 -11
  160. package/dist/multi-agent/memory-agent-persona.d.ts.map +0 -1
  161. package/dist/multi-agent/memory-agent-persona.js +0 -175
  162. package/dist/multi-agent/memory-agent-persona.js.map +0 -1
  163. package/dist/operator/report-context.d.ts +0 -184
  164. package/dist/operator/report-context.d.ts.map +0 -1
  165. package/dist/operator/report-context.js +0 -1052
  166. package/dist/operator/report-context.js.map +0 -1
@@ -50,6 +50,7 @@ exports.causeFromOwnerMessage = causeFromOwnerMessage;
50
50
  exports.protectImageAnalysis = protectImageAnalysis;
51
51
  exports.buildUploadedMediaInstructions = buildUploadedMediaInstructions;
52
52
  exports.hashSessionPolicyFingerprint = hashSessionPolicyFingerprint;
53
+ exports.projectOwnerConversationAgency = projectOwnerConversationAgency;
53
54
  exports.agentSavedInTurn = agentSavedInTurn;
54
55
  exports.createMockAgentLoop = createMockAgentLoop;
55
56
  const node_path_1 = require("node:path");
@@ -61,6 +62,7 @@ const member_effective_scope_js_1 = require("./member-effective-scope.js");
61
62
  const session_pool_js_1 = require("../agent/session-pool.js");
62
63
  const agent_loop_js_1 = require("../agent/agent-loop.js");
63
64
  const console_brief_js_1 = require("../operator/console-brief.js");
65
+ const owner_runtime_js_1 = require("../operator/owner-runtime.js");
64
66
  const role_manager_js_1 = require("../agent/role-manager.js");
65
67
  const gateway_tool_catalog_js_1 = require("../agent/gateway-tool-catalog.js");
66
68
  const private_prompt_overlay_js_1 = require("../connectors/private-prompt-overlay.js");
@@ -68,7 +70,6 @@ const context_prompt_builder_js_1 = require("../agent/context-prompt-builder.js"
68
70
  const prompt_enhancer_js_1 = require("../agent/prompt-enhancer.js");
69
71
  const principal_js_1 = require("./principal.js");
70
72
  const debugLogger = __importStar(require("@jungjaehoon/mama-core/debug-logger"));
71
- const audit_task_queue_js_1 = require("../memory/audit-task-queue.js");
72
73
  const agent_notice_queue_js_1 = require("../memory/agent-notice-queue.js");
73
74
  const scope_context_js_1 = require("../memory/scope-context.js");
74
75
  const learning_context_js_1 = require("../operator/learning-context.js");
@@ -76,7 +77,6 @@ const learning_read_js_1 = require("../operator/learning-read.js");
76
77
  const turn_observer_js_1 = require("../operator/turn-observer.js");
77
78
  const learning_markers_js_1 = require("../operator/learning-markers.js");
78
79
  const recall_bundle_formatter_js_1 = require("../memory/recall-bundle-formatter.js");
79
- const save_candidate_extractor_js_1 = require("../memory/save-candidate-extractor.js");
80
80
  const untrusted_content_js_1 = require("../utils/untrusted-content.js");
81
81
  const security_monitor_js_1 = require("../security/security-monitor.js");
82
82
  const agent_store_js_1 = require("../db/agent-store.js");
@@ -221,12 +221,19 @@ const OWNER_CONSOLE_OPERATING_DISCIPLINE = `## Owner console operating disciplin
221
221
  - You are the owner's operator, not an advisor. Report what you found and what you did; do not hand back work you could have done yourself.
222
222
  - Gather before answering. Any question about status, work, or what happened is answered by CALLING your gateway tools first. Never reply "please check X" when a gateway tool can check X - check it, then report.
223
223
  - Never claim a check you did not run. If a tool errored or was denied, name the tool and the error; a missing answer is reported as missing, never smoothed over.
224
- - Act by default; ask only before the irreversible. Reversible work is yours to do and then report: reading, analysing, ranking, drafting, and writes whose only audience is this console (mama_save, task_create/task_update on your task board, report_request). Ask first only when the effect leaves this conversation and cannot be taken back - sending to another channel (telegram_send), uploading or overwriting shared files (drive_upload), storing credentials, or delegating a run you cannot cancel.
224
+ - Act by default; ask only before the irreversible. Reversible work is yours to do and then report: reading, analysing, ranking, drafting, and writes whose only audience is this console (mama_save, task_create/task_update on your task board). Ask first only when the effect leaves this conversation and cannot be taken back - sending to another channel (telegram_send), uploading or overwriting shared files (drive_upload), storing credentials, or delegating a run you cannot cancel.
225
+ - A status or full-report request is YOUR judgment task in THIS conversation. Use compact descriptors and overview reads first, deepen only the material uncertainties through bounded pages, take authorized reversible actions, and answer the owner directly. Never hand it to another report agent and never reply with only an acknowledgement.
225
226
  - Do not close by offering work you could have done. "Shall I rank these?" or "want it broken down by person?" is work, not a question - if you can do it now it belongs in this reply. End with what you did and what the OWNER has to decide, never with a menu of things you are willing to do next.
226
227
  - Multi-step requests: decide the steps, carry out the reversible ones, then report the outcome. Do not return the plan as a suggestion and stop.
227
228
  - Synthesize, do not dump. A raw tool result is evidence, not an answer - say what it means for the owner and cite which source answered.
228
229
  - Before saying something is done, verify it (re-read the artifact or re-run the query) and say what you verified.
229
230
  - Reply in the language the owner writes in.`;
231
+ function projectOwnerConversationAgency(roleName, role) {
232
+ if (roleName !== 'owner_console') {
233
+ return role;
234
+ }
235
+ return (0, owner_runtime_js_1.projectOwnerRuntimeRole)(role);
236
+ }
230
237
  /** Channel-less host alarms (workorder failures) park here; every resumed
231
238
  * owner turn reads this key in addition to its own channel key. */
232
239
  exports.OPERATOR_BROADCAST_NOTICE_KEY = 'operator:broadcast';
@@ -268,7 +275,7 @@ const MEMBER_BLOCKED_TOOLS = [
268
275
  'Read',
269
276
  'Write',
270
277
  'Bash',
271
- 'delegate',
278
+ 'native_subagent',
272
279
  'discord_send',
273
280
  'slack_send',
274
281
  'telegram_send',
@@ -276,7 +283,6 @@ const MEMBER_BLOCKED_TOOLS = [
276
283
  'task_create',
277
284
  'task_update',
278
285
  'task_reclassify',
279
- 'report_request',
280
286
  'report_publish',
281
287
  'wiki_read',
282
288
  'wiki_publish',
@@ -358,9 +364,6 @@ function sanitizeForPrompt(text) {
358
364
  .replace(/</g, '&lt;')
359
365
  .replace(/>/g, '&gt;');
360
366
  }
361
- function stripGatewayDecorations(text) {
362
- return text.replace(/^(?:\s*\|\|[\s\S]*?\|\|\s*)+/u, '').trim();
363
- }
364
367
  /**
365
368
  * True when the agent persisted memory during THIS turn (gateway mama_save in
366
369
  * the reasoning header) - the extractor safety net then skips to avoid the
@@ -406,28 +409,11 @@ class MessageRouter {
406
409
  envelopeConfig;
407
410
  envelopeAuthority;
408
411
  privateConnectorPolicy;
409
- reportCarry;
410
- ownerReportInbox;
411
412
  memberScopeResolver;
412
413
  roleManager;
413
414
  promptEnhancer;
414
- gatewayRegistry = null;
415
- memoryAgentProcessManager;
416
- memoryAuditQueue;
417
415
  memoryNoticeQueue = new agent_notice_queue_js_1.AgentNoticeQueue();
418
- memoryAuditCooldowns = new Map();
419
416
  channelTails = new Map();
420
- memoryAgentStats = {
421
- turnsObserved: 0,
422
- candidatesDetected: 0,
423
- factsExtracted: 0,
424
- factsSaved: 0,
425
- acksApplied: 0,
426
- acksSkipped: 0,
427
- acksFailed: 0,
428
- lastExtraction: null,
429
- recentExtractions: [],
430
- };
431
417
  // Sessions DB for MAMA frontdoor activity logging
432
418
  sessionsDb = null;
433
419
  setSessionsDb(db) {
@@ -438,11 +424,7 @@ class MessageRouter {
438
424
  setUICommandQueue(queue) {
439
425
  this.uiCommandQueue = queue;
440
426
  }
441
- // Validation service for memory agent + MAMA sessions
442
- validationService = null;
443
- setValidationService(svc) {
444
- this.validationService = svc;
445
- }
427
+ setValidationService(_svc) { }
446
428
  getPageContextPrefix(message) {
447
429
  if (!this.uiCommandQueue) {
448
430
  return '';
@@ -518,30 +500,19 @@ class MessageRouter {
518
500
  lines.push('</viewer-context>');
519
501
  return lines.join('\n') + '\n';
520
502
  }
521
- setGatewayRegistry(registry) {
522
- this.gatewayRegistry = registry;
523
- }
524
- setMemoryAgent(processManager) {
525
- this.memoryAgentProcessManager = processManager;
526
- this.memoryAuditQueue = new audit_task_queue_js_1.AuditTaskQueue(async (job) => {
527
- const process = await processManager.getSharedProcess('memory');
528
- const result = await process.sendMessage(this.buildMemoryAuditPrompt(job), {
529
- sourceTurnId: job.turnId,
530
- sourceMessageRef: [job.source, job.channelId, job.turnId].filter(Boolean).join(':'),
531
- parentModelRunId: job.parentModelRunId,
532
- });
533
- if (result &&
534
- typeof result === 'object' &&
535
- 'ack' in result &&
536
- result.ack &&
537
- typeof result.ack === 'object') {
538
- return result.ack;
539
- }
540
- return this.classifyMemoryAuditResponse(typeof result?.response === 'string' ? result.response : '');
541
- });
542
- }
503
+ setGatewayRegistry(_registry) { }
543
504
  getMemoryAgentStats() {
544
- return { ...this.memoryAgentStats };
505
+ return {
506
+ turnsObserved: 0,
507
+ candidatesDetected: 0,
508
+ factsExtracted: 0,
509
+ factsSaved: 0,
510
+ acksApplied: 0,
511
+ acksSkipped: 0,
512
+ acksFailed: 0,
513
+ lastExtraction: null,
514
+ recentExtractions: [],
515
+ };
545
516
  }
546
517
  /**
547
518
  * Host-code enqueue into the owner notice surface (Stage-2 workorder
@@ -561,39 +532,6 @@ class MessageRouter {
561
532
  relevant_memories: [],
562
533
  });
563
534
  }
564
- /**
565
- * Public API for auditing a conversation via the memory agent.
566
- * Used by the /api/mama/audit-conversation endpoint for benchmarking.
567
- * Bypasses cooldown and candidate detection — caller provides conversation + optional candidates.
568
- */
569
- async auditConversation(job) {
570
- if (!this.memoryAuditQueue) {
571
- throw new Error('Memory agent not initialized — cannot audit conversation');
572
- }
573
- const auditJob = {
574
- turnId: `audit_${Date.now()}`,
575
- channelKey: 'api:default',
576
- source: 'api',
577
- scopeContext: job.scopes.map((s) => ({
578
- kind: s.kind,
579
- id: s.id,
580
- })),
581
- conversation: job.conversation,
582
- candidates: job.candidates?.map((c) => ({
583
- id: `api_${Date.now()}`,
584
- kind: c.kind,
585
- topicHint: c.topicHint,
586
- confidence: c.confidence,
587
- summary: c.summary,
588
- evidence: [c.summary],
589
- channelKey: 'api:default',
590
- source: 'api',
591
- channelId: 'default',
592
- createdAt: Date.now(),
593
- })),
594
- };
595
- return this.memoryAuditQueue.enqueue(auditJob);
596
- }
597
535
  constructor(sessionStore, agentLoop, mamaApi, config = {}, envelopeConfig, envelopeAuthority, dependencies = {}) {
598
536
  this.sessionStore = sessionStore;
599
537
  this.agentLoop = agentLoop;
@@ -605,8 +543,6 @@ class MessageRouter {
605
543
  this.envelopeAuthority = envelopeAuthority;
606
544
  this.privateConnectorPolicy =
607
545
  dependencies.privateConnectorPolicy ?? DEFAULT_PRIVATE_CONNECTOR_POLICY;
608
- this.reportCarry = dependencies.reportCarry;
609
- this.ownerReportInbox = dependencies.ownerReportInbox;
610
546
  this.memberScopeResolver = dependencies.memberScopeResolver;
611
547
  if (this.envelopeConfig && !this.envelopeAuthority) {
612
548
  throw new Error('[envelope] ReactiveEnvelopeConfig provided without EnvelopeAuthority');
@@ -647,19 +583,16 @@ class MessageRouter {
647
583
  createAgentContext(message, sessionId) {
648
584
  // Per-message trust context: chatType is runtime state (a locked allowlist
649
585
  // alone cannot prove owner - allowlisted groups contain third parties).
650
- const { roleName, role } = this.roleManager.getRoleForSource(message.source, {
651
- channelId: message.channelId,
652
- chatType: typeof message.metadata?.chatType === 'string' ? message.metadata.chatType : undefined,
653
- principal: message.principal,
654
- });
586
+ const { roleName, role } = this.resolveMessageRole(message);
655
587
  const surface = roleName === 'owner_console'
656
588
  ? 'owner_console'
657
589
  : roleName === 'os_agent'
658
590
  ? 'os_agent'
659
591
  : 'multi-agent-generic';
660
- const projectedRole = message.principal?.class === 'member'
592
+ const connectorProjectedRole = message.principal?.class === 'member'
661
593
  ? memberReadOnlyRole(role)
662
594
  : this.privateConnectorPolicy.projectRole(surface, role);
595
+ const projectedRole = projectOwnerConversationAgency(roleName, connectorProjectedRole);
663
596
  const capabilities = this.roleManager.getCapabilities(projectedRole);
664
597
  const limitations = this.roleManager.getLimitations(projectedRole);
665
598
  const ctx = (0, context_prompt_builder_js_1.createAgentContext)(message.source, roleName, projectedRole, {
@@ -677,6 +610,13 @@ class MessageRouter {
677
610
  }
678
611
  return ctx;
679
612
  }
613
+ resolveMessageRole(message) {
614
+ return this.roleManager.getRoleForSource(message.source, {
615
+ channelId: message.channelId,
616
+ chatType: typeof message.metadata?.chatType === 'string' ? message.metadata.chatType : undefined,
617
+ principal: message.principal,
618
+ });
619
+ }
680
620
  /**
681
621
  * Check if message should trigger auto-translation
682
622
  * Returns true for short messages or image-related text
@@ -766,14 +706,17 @@ class MessageRouter {
766
706
  });
767
707
  assertUsableMemberEffectiveScope(memberEffectiveScope, admittedPrincipal.principalId ?? '');
768
708
  }
769
- const channelKey = (0, session_pool_js_1.buildChannelKey)(admittedMessage.source, (0, principal_js_1.laneChannelId)(admittedMessage.channelId, lane));
770
- const previous = this.channelTails.get(channelKey);
709
+ const admissionRoleName = this.resolveMessageRole(admittedMessage).roleName;
710
+ const admissionKey = admissionRoleName === 'owner_console'
711
+ ? owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY
712
+ : (0, session_pool_js_1.buildChannelKey)(admittedMessage.source, (0, principal_js_1.laneChannelId)(admittedMessage.channelId, lane));
713
+ const previous = this.channelTails.get(admissionKey);
771
714
  let release;
772
715
  const gate = new Promise((resolve) => {
773
716
  release = resolve;
774
717
  });
775
718
  const currentTail = (previous ?? Promise.resolve()).catch(() => { }).then(() => gate);
776
- this.channelTails.set(channelKey, currentTail);
719
+ this.channelTails.set(admissionKey, currentTail);
777
720
  try {
778
721
  if (previous) {
779
722
  processOptions?.onQueued?.();
@@ -786,8 +729,8 @@ class MessageRouter {
786
729
  }
787
730
  finally {
788
731
  release();
789
- if (this.channelTails.get(channelKey) === currentTail) {
790
- this.channelTails.delete(channelKey);
732
+ if (this.channelTails.get(admissionKey) === currentTail) {
733
+ this.channelTails.delete(admissionKey);
791
734
  }
792
735
  }
793
736
  }
@@ -853,10 +796,27 @@ This protects your credentials from being exposed in chat logs.`;
853
796
  }
854
797
  // 1. Get or create session (by source + channelId)
855
798
  const session = this.sessionStore.getOrCreate(message.source, sessionChannelId, message.userId, message.channelName);
799
+ const agentContext = this.createAgentContext(message, session.id);
800
+ const ownerRuntimeRole = agentContext.roleName === 'owner_console'
801
+ ? projectOwnerConversationAgency('owner_console', this.resolveMessageRole(message).role)
802
+ : agentContext.role;
856
803
  // 2. Check if session is busy (another request in progress)
857
804
  const channelKey = (0, session_pool_js_1.buildChannelKey)(message.source, sessionChannelId);
805
+ const runtimeSessionKey = agentContext.roleName === 'owner_console' ? owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY : channelKey;
858
806
  const sessionPool = (0, session_pool_js_1.getSessionPool)();
859
- const initialSession = sessionPool.getSession(channelKey);
807
+ const runtimeOwnsSession = runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY && this.agentLoop.managesOwnerQueue === true;
808
+ const ownerSession = runtimeOwnsSession
809
+ ? sessionPool.peekSession(runtimeSessionKey)
810
+ : undefined;
811
+ const initialSession = runtimeOwnsSession
812
+ ? {
813
+ sessionId: ownerSession?.sessionId ?? session.id,
814
+ isNew: !ownerSession?.sessionId,
815
+ busy: false,
816
+ }
817
+ : sessionPool.getSession(runtimeSessionKey);
818
+ if (ownerSession?.busy)
819
+ processOptions?.onQueued?.();
860
820
  let cliSessionId = initialSession.sessionId;
861
821
  let isNewCliSession = initialSession.isNew;
862
822
  const busy = initialSession.busy;
@@ -864,12 +824,13 @@ This protects your credentials from being exposed in chat logs.`;
864
824
  // assignment sits inside the agent-run block and is not visible at the return.
865
825
  let completedModelRunId = null;
866
826
  let completedProvenanceReason = 'backend_no_run';
827
+ let completedOwnerJournalProvenance;
867
828
  const sourceTurnId = message.metadata?.messageId ?? `generated:${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
868
829
  const sourceMessageRef = [message.source, message.channelId, sourceTurnId]
869
830
  .filter(Boolean)
870
831
  .join(':');
871
832
  // Track lock ownership for proper cleanup in finally block
872
- let acquiredLock = !busy; // If not busy, we acquired lock from initialSession
833
+ let acquiredLock = !runtimeOwnsSession && !busy;
873
834
  // If session is busy, notify caller immediately and wait for it to be released
874
835
  if (busy) {
875
836
  logger.debug(`Session busy for ${channelKey}, notifying client`);
@@ -882,7 +843,7 @@ This protects your credentials from being exposed in chat logs.`;
882
843
  while (Date.now() - waitStart < maxWaitMs) {
883
844
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
884
845
  // Use read-only peek to avoid side effects (no lock increment)
885
- const check = sessionPool.peekSession(channelKey);
846
+ const check = sessionPool.peekSession(runtimeSessionKey);
886
847
  if (!check.busy) {
887
848
  logger.debug(`Session released for ${channelKey} after ${Math.round((Date.now() - waitStart) / 1000)}s`);
888
849
  break;
@@ -894,7 +855,7 @@ This protects your credentials from being exposed in chat logs.`;
894
855
  }
895
856
  }
896
857
  // Re-acquire session after wait (properly locks it this time)
897
- const reacquired = sessionPool.getSession(channelKey);
858
+ const reacquired = sessionPool.getSession(runtimeSessionKey);
898
859
  if (reacquired.busy) {
899
860
  // Still busy after timeout - we never acquired the lock
900
861
  throw new Error(`Session for ${channelKey} timed out after ${maxWaitMs / 1000}s`);
@@ -907,35 +868,17 @@ This protects your credentials from being exposed in chat logs.`;
907
868
  const releaseCliSessionLock = () => {
908
869
  if (!acquiredLock || lockReleased)
909
870
  return;
910
- sessionPool.releaseSession(channelKey, cliSessionId);
871
+ sessionPool.releaseSession(runtimeSessionKey, cliSessionId);
911
872
  lockReleased = true;
912
873
  };
913
874
  try {
914
- const agentContext = this.createAgentContext(message, session.id);
915
- const reportCarryTarget = agentContext.roleName === 'owner_console' && message.source === 'telegram'
916
- ? { source: message.source, channelId: message.channelId }
917
- : null;
918
- const reportCarryChannelKey = reportCarryTarget
919
- ? (0, session_pool_js_1.buildChannelKey)(reportCarryTarget.source, (0, principal_js_1.laneChannelId)(reportCarryTarget.channelId, lane))
920
- : null;
921
- const reportCarryPeek = reportCarryTarget
922
- ? (this.reportCarry?.peek(reportCarryTarget) ?? null)
923
- : null;
924
- // TG-05: inbox lookup only AFTER the owner-console trust check
925
- // (reportCarryTarget is non-null only for a verified owner turn). The
926
- // snapshot is captured once; its exact ordered IDs are the only
927
- // consumable set for this turn.
928
- const ownerReportSnapshot = reportCarryTarget
929
- ? (this.ownerReportInbox?.snapshot(reportCarryTarget) ?? null)
930
- : null;
931
875
  const mediaInstructions = buildUploadedMediaInstructions(message, agentContext.role.allowedTools, agentContext.roleName === 'owner_console');
932
- // Save user message immediately for crash/refresh resilience. A
933
- // report-consuming turn is provisional until its atomic final commit.
876
+ // Save user message immediately for crash/refresh resilience.
934
877
  this.sessionStore.appendMessage(session.id, {
935
878
  role: 'user',
936
879
  content: [message.text, mediaInstructions].filter(Boolean).join('\n\n'),
937
880
  timestamp: Date.now(),
938
- }, ownerReportSnapshot ? { sourceMessageRef } : {});
881
+ });
939
882
  let response = '';
940
883
  let context = { prompt: '', decisions: [], hasContext: false };
941
884
  let streamFlushTimer = null;
@@ -948,7 +891,6 @@ This protects your credentials from being exposed in chat logs.`;
948
891
  // CONTINUE turns: Codex server retains full conversation via threadId,
949
892
  // so skip expensive prompt rebuilding (embedding search, DB history, etc.)
950
893
  let systemPrompt;
951
- let ownerReportHistoryPrompt = '';
952
894
  const historyContext = message.metadata?.historyContext;
953
895
  // Public turns never inspect private workspace rules, skills, or AGENTS files.
954
896
  const enhanced = isPublicLane
@@ -957,7 +899,13 @@ This protects your credentials from being exposed in chat logs.`;
957
899
  // TG-05: every main backend keeps its live conversation in a durable
958
900
  // runtime session. Only a genuinely new/replacement session rebuilds
959
901
  // bounded MAMA context.
960
- const needsFullContext = isNewCliSession;
902
+ // The in-memory SessionPool is empty after a daemon restart, while a
903
+ // durable Codex thread may still be alive. Codex first attempts that
904
+ // thread with a minimal continuation; AgentLoop invokes the lazy full
905
+ // builder only when the registry says the thread is actually missing.
906
+ const lazyDurableResume = runtimeOwnsSession ||
907
+ (this.config.backend === 'codex' && this.agentLoop.probesDurableSession === true);
908
+ const needsFullContext = isNewCliSession && !lazyDurableResume;
961
909
  // Persistent backends skip repeated retrieval after the first turn.
962
910
  context =
963
911
  !isPublicLane && needsFullContext && this.config.implicitLegacyContextSearch
@@ -982,8 +930,8 @@ This protects your credentials from being exposed in chat logs.`;
982
930
  source: message.source,
983
931
  channelId: message.channelId,
984
932
  });
985
- systemPrompt = this.buildSystemPrompt(session, context.prompt, historyContext, sessionStartupContext, agentContext, enhanced, true, trelloAvailable);
986
- ownerReportHistoryPrompt = this.buildOwnerReportHistoryPrompt(session);
933
+ systemPrompt = this.buildSystemPrompt(session, context.prompt, historyContext, sessionStartupContext, agentContext, enhanced, !(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY &&
934
+ this.agentLoop.ownerRecoveryJournalEnabled === true), trelloAvailable);
987
935
  }
988
936
  }
989
937
  // 7. Run agent loop (with session info for lane-based concurrency)
@@ -1018,13 +966,14 @@ This protects your credentials from being exposed in chat logs.`;
1018
966
  // After a daemon restart the pool is new, but the durable Codex thread may
1019
967
  // still exist and should be resumed. Keep prompt/context freshness separate
1020
968
  // from backend conversation continuity.
1021
- const shouldResumeBackend = this.config.backend === 'codex' ? true : shouldResume;
969
+ const shouldResumeBackend = runtimeOwnsSession || this.config.backend === 'codex' ? true : shouldResume;
970
+ const usesContinuationContext = shouldResume || lazyDurableResume;
1022
971
  // For resumed sessions: inject minimal context only
1023
972
  // Persistent CLI keeps the process alive with full system prompt from initial request
1024
973
  // Only inject per-message context (related decisions) to avoid context overflow
1025
974
  const effectivePrompt = isPublicLane
1026
975
  ? publicLaneBaseInstructions
1027
- : shouldResume
976
+ : usesContinuationContext
1028
977
  ? this.buildMinimalResumePrompt(context.prompt, agentContext)
1029
978
  : systemPrompt;
1030
979
  // Wrap stream callbacks to accumulate deltas and periodically flush to DB
@@ -1047,7 +996,6 @@ This protects your credentials from being exposed in chat logs.`;
1047
996
  }
1048
997
  }, streamFlushIntervalMs);
1049
998
  }
1050
- let parentModelRunId;
1051
999
  // Skill on-demand injection: prepend matched skill content to user message
1052
1000
  // (not system prompt — PersistentCLI can't update system prompt after creation)
1053
1001
  const skillPrefix = !isPublicLane && enhanced.skillContent
@@ -1067,22 +1015,28 @@ This protects your credentials from being exposed in chat logs.`;
1067
1015
  let pendingNotices = false;
1068
1016
  let pendingChannelNoticeCount = 0;
1069
1017
  let pendingBroadcastNoticeCount = 0;
1070
- const envelope = this.buildReactiveEnvelope(message, agentContext, memberEffectiveScope);
1018
+ const envelope = runtimeOwnsSession
1019
+ ? undefined
1020
+ : this.buildReactiveEnvelope(message, agentContext, memberEffectiveScope);
1071
1021
  const options = {
1072
1022
  systemPrompt: effectivePrompt,
1073
- ...(ownerReportHistoryPrompt ? { ownerReportHistoryPrompt } : {}),
1074
1023
  sessionPolicyFingerprint,
1075
1024
  ...(memberEffectiveScope ? { memberEffectiveScope } : {}),
1076
1025
  ...(memberEffectiveScope ? { memberScopeRequired: true } : {}),
1077
1026
  userId: message.userId,
1078
1027
  model: roleModel, // Role-specific model override
1079
1028
  maxTurns: roleMaxTurns, // Role-specific max turns
1080
- sessionKey: channelKey,
1029
+ sessionKey: runtimeSessionKey,
1030
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY ? { lanePriority: 100 } : {}),
1081
1031
  source: message.source,
1082
1032
  channelId: message.channelId,
1083
1033
  agentContext,
1034
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY
1035
+ ? { sessionPolicyRole: ownerRuntimeRole }
1036
+ : {}),
1084
1037
  resumeSession: shouldResumeBackend,
1085
- cliSessionId, // Pass CLI session ID to avoid double-locking
1038
+ // Queued owner input must not claim a busy session or pin a stale pool id.
1039
+ ...(runtimeOwnsSession ? {} : { cliSessionId }),
1086
1040
  // AgentLoop may replace a stale backend session while this request still
1087
1041
  // owns the channel lock. Keep cleanup guarded by the current owner ID.
1088
1042
  onCliSessionReset: (sessionId) => {
@@ -1090,8 +1044,16 @@ This protects your credentials from being exposed in chat logs.`;
1090
1044
  },
1091
1045
  streamCallbacks: wrappedOnStream || processOptions?.onStream,
1092
1046
  envelope,
1047
+ ...(runtimeOwnsSession
1048
+ ? {
1049
+ prepareEnvelope: () => this.buildReactiveEnvelope(message, agentContext, memberEffectiveScope),
1050
+ }
1051
+ : {}),
1093
1052
  sourceTurnId,
1094
1053
  sourceMessageRef,
1054
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY
1055
+ ? { ownerJournalPrompt: message.text, ownerJournalTrust: 'owner' }
1056
+ : {}),
1095
1057
  // The owner's message is what caused whatever this turn changes, and the router
1096
1058
  // knows it BEFORE the agent runs - the same shape as a reconcile work order
1097
1059
  // carrying its delta batch. Without this the chat lane had no cause wire at all:
@@ -1104,7 +1066,7 @@ This protects your credentials from being exposed in chat logs.`;
1104
1066
  // citation this ledger exists to refuse.
1105
1067
  ...(causeFromOwnerMessage(sourceTurnId, sourceMessageRef) ?? {}),
1106
1068
  };
1107
- if (shouldResume) {
1069
+ if (shouldResumeBackend) {
1108
1070
  options.freshSessionSystemPrompt = async () => {
1109
1071
  if (isPublicLane) {
1110
1072
  return memberEffectiveScope
@@ -1118,27 +1080,21 @@ This protects your credentials from being exposed in chat logs.`;
1118
1080
  source: message.source,
1119
1081
  channelId: message.channelId,
1120
1082
  });
1121
- return this.buildSystemPrompt(session, freshContext.prompt, historyContext, sessionStartupContext, agentContext, enhanced, true, trelloAvailable);
1083
+ return this.buildSystemPrompt(session, freshContext.prompt, historyContext, sessionStartupContext, agentContext, enhanced, !(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY &&
1084
+ this.agentLoop.ownerRecoveryJournalEnabled === true), trelloAvailable);
1122
1085
  };
1123
- if (!isPublicLane) {
1124
- options.freshSessionOwnerReportHistoryPrompt = () => this.buildOwnerReportHistoryPrompt(session);
1125
- }
1126
1086
  }
1127
- if (shouldResume) {
1087
+ if (usesContinuationContext) {
1128
1088
  logger.info(`Resuming CLI session (minimal: ${effectivePrompt.length} chars)`);
1129
1089
  }
1130
1090
  else {
1131
1091
  logger.info(`New CLI session (full: ${systemPrompt.length} chars)`);
1132
1092
  }
1133
- // TG-05/TG-06: carry is captured once before the model call and travels
1134
- // only in this turn's user message, never in a rebuilt system prompt.
1135
- const carryPrefix = !isPublicLane ? (reportCarryPeek?.prefix ?? '') : '';
1136
- const inboxPrefix = !isPublicLane && ownerReportSnapshot ? `${ownerReportSnapshot.text}\n\n` : '';
1137
1093
  try {
1138
1094
  if (isPublicLane) {
1139
1095
  memoryPrefix = '';
1140
1096
  }
1141
- else if (shouldResume) {
1097
+ else if (usesContinuationContext) {
1142
1098
  // Per-channel notices PLUS the operator broadcast key: host-code
1143
1099
  // alarms (workorder failures) have no conversation channel at
1144
1100
  // enqueue time - without the broadcast read they dead-letter
@@ -1166,7 +1122,7 @@ This protects your credentials from being exposed in chat logs.`;
1166
1122
  }
1167
1123
  catch (err) {
1168
1124
  logger.warn(`[memory-prefix] Failed: ${err instanceof Error ? err.message : String(err)}`);
1169
- if (!isPublicLane && shouldResume) {
1125
+ if (!isPublicLane && usesContinuationContext) {
1170
1126
  try {
1171
1127
  memoryPrefix = await this.getPerTurnMemoryPrefix(message);
1172
1128
  }
@@ -1212,7 +1168,7 @@ This protects your credentials from being exposed in chat logs.`;
1212
1168
  }
1213
1169
  // Add text content (with memory context, skill context, and page context)
1214
1170
  const pageCtx = isPublicLane ? '' : this.getPageContextPrefix(message);
1215
- const effectiveMessageText = `${pageCtx}${inboxPrefix}${carryPrefix}${memoryPrefix}${learningPrefix}${skillPrefix}${messageText || ''}`;
1171
+ const effectiveMessageText = `${pageCtx}${memoryPrefix}${learningPrefix}${skillPrefix}${messageText || ''}`;
1216
1172
  if (effectiveMessageText) {
1217
1173
  contentBlocks.push({ type: 'text', text: effectiveMessageText });
1218
1174
  }
@@ -1247,48 +1203,27 @@ This protects your credentials from being exposed in chat logs.`;
1247
1203
  const turnStart = Date.now();
1248
1204
  const result = await this.agentLoop.runWithContent(contentBlocks, options);
1249
1205
  response = result.response;
1250
- parentModelRunId = result.modelRunId ?? undefined;
1251
1206
  completedModelRunId = result.modelRunId ?? completedModelRunId;
1252
1207
  if (result.modelRunProvenance === 'commit_failed') {
1253
1208
  completedProvenanceReason = 'commit_failed';
1254
1209
  }
1210
+ completedOwnerJournalProvenance = result.ownerJournalProvenance;
1255
1211
  this.logFrontdoorActivity(message, message.text, response, Date.now() - turnStart);
1256
1212
  }
1257
1213
  else {
1258
1214
  const pageCtx = isPublicLane ? '' : this.getPageContextPrefix(message);
1259
- const effectiveText = `${pageCtx}${inboxPrefix}${carryPrefix}${memoryPrefix}${learningPrefix}${skillPrefix}${message.text}`;
1215
+ const effectiveText = `${pageCtx}${memoryPrefix}${learningPrefix}${skillPrefix}${message.text}`;
1260
1216
  const turnStart = Date.now();
1261
1217
  const result = await this.agentLoop.run(effectiveText, options);
1262
1218
  response = result.response;
1263
- parentModelRunId = result.modelRunId ?? undefined;
1264
1219
  completedModelRunId = result.modelRunId ?? completedModelRunId;
1265
1220
  if (result.modelRunProvenance === 'commit_failed') {
1266
1221
  completedProvenanceReason = 'commit_failed';
1267
1222
  }
1223
+ completedOwnerJournalProvenance = result.ownerJournalProvenance;
1268
1224
  this.logFrontdoorActivity(message, message.text, response, Date.now() - turnStart);
1269
1225
  }
1270
- // Auto-extract facts from conversation (fire-and-forget, non-blocking).
1271
- // Dual-save dedup: when the agent ALREADY persisted memory during this
1272
- // turn (gateway mama_save in the reasoning header), the extractor safety
1273
- // net would save the same instruction twice (proven live 2026-07-17:
1274
- // decision_* + mem_* duplicates for one directive). The in-turn save is
1275
- // the agent's judgment; the net exists for turns where it did not act.
1276
- const agentSavedThisTurn = agentSavedInTurn(response);
1277
- if (agentSavedThisTurn) {
1278
- logger.info('[memory-agent] extraction skipped - agent saved in-turn (dual-save dedup)');
1279
- }
1280
- if (!isPublicLane && response && message.text && !agentSavedThisTurn) {
1281
- const rawAssistantText = stripGatewayDecorations(response);
1282
- void (async () => {
1283
- try {
1284
- await this.triggerMemoryAgent(channelKey, message.text, rawAssistantText, message, sourceTurnId, sourceMessageRef, parentModelRunId);
1285
- }
1286
- catch {
1287
- /* non-fatal */
1288
- }
1289
- })();
1290
- }
1291
- if (shouldResume && pendingNotices) {
1226
+ if (usesContinuationContext && pendingNotices) {
1292
1227
  // Drain each queue by ITS OWN peeked count - notices enqueued while
1293
1228
  // the agent run was in flight stay for the next turn (review N2).
1294
1229
  if (pendingChannelNoticeCount > 0) {
@@ -1312,7 +1247,7 @@ This protects your credentials from being exposed in chat logs.`;
1312
1247
  normalizedErrorMsg.includes('exited with code');
1313
1248
  if (isCriticalError) {
1314
1249
  logger.warn(`CLI error detected, invalidating session: ${errorMsg}`);
1315
- sessionPool.invalidateSession(channelKey, cliSessionId);
1250
+ sessionPool.invalidateSession(runtimeSessionKey, cliSessionId);
1316
1251
  }
1317
1252
  // Release session lock before re-throwing
1318
1253
  releaseCliSessionLock();
@@ -1358,36 +1293,14 @@ This protects your credentials from being exposed in chat logs.`;
1358
1293
  });
1359
1294
  }
1360
1295
  // 6. Update session context — finalize assistant response
1361
- if (ownerReportSnapshot) {
1362
- // TG-05 (design Decision 6): the final turn, its receipt, and the
1363
- // consumption marks commit in ONE SQLite transaction. A failure here
1364
- // MUST surface - no report caller may swallow persistence failure and
1365
- // report success.
1366
- this.sessionStore.finalizeTurnWithReportReceipt({
1367
- sessionId: session.id,
1368
- sourceMessageRef,
1369
- finalResponse: response,
1370
- committedAtIso: new Date().toISOString(),
1371
- receipt: {
1372
- deliveryIds: ownerReportSnapshot.deliveryIds,
1373
- projectionVersion: ownerReportSnapshot.version,
1374
- projectionText: ownerReportSnapshot.text,
1375
- projectionHash: ownerReportSnapshot.projectionHash,
1376
- },
1296
+ const persisted = this.sessionStore.flushStreamingResponse(session.id, response) ||
1297
+ this.sessionStore.appendMessage(session.id, {
1298
+ role: 'assistant',
1299
+ content: response,
1300
+ timestamp: Date.now(),
1377
1301
  });
1378
- }
1379
- else {
1380
- // Use flushStreamingResponse first (updates existing turn from periodic flush),
1381
- // fall back to appendMessage if no turn exists yet (non-streaming path)
1382
- const persisted = this.sessionStore.flushStreamingResponse(session.id, response) ||
1383
- this.sessionStore.appendMessage(session.id, {
1384
- role: 'assistant',
1385
- content: response,
1386
- timestamp: Date.now(),
1387
- });
1388
- if (!persisted) {
1389
- throw new Error('Unable to persist final assistant response');
1390
- }
1302
+ if (!persisted) {
1303
+ throw new Error('Unable to persist final assistant response');
1391
1304
  }
1392
1305
  if ((message.principal?.lane ?? 'owner') !== 'public' &&
1393
1306
  response &&
@@ -1400,19 +1313,6 @@ This protects your credentials from being exposed in chat logs.`;
1400
1313
  logger.warn(`[learning] observer failed: ${error instanceof Error ? error.message : String(error)}`);
1401
1314
  });
1402
1315
  }
1403
- if (reportCarryPeek && reportCarryTarget && reportCarryChannelKey) {
1404
- try {
1405
- this.reportCarry?.acknowledge({
1406
- deliveryId: reportCarryPeek.deliveryId,
1407
- target: reportCarryTarget,
1408
- consumingChannelKey: reportCarryChannelKey,
1409
- consumedAtIso: new Date().toISOString(),
1410
- });
1411
- }
1412
- catch (error) {
1413
- logger.warn(`[report-carry] acknowledgement failed: ${error instanceof Error ? error.message : String(error)}`);
1414
- }
1415
- }
1416
1316
  // Release session lock AFTER final persistence to prevent out-of-order turns
1417
1317
  releaseCliSessionLock();
1418
1318
  // 6. Return result. Run identity rides out with it: the router already generated
@@ -1429,6 +1329,14 @@ This protects your credentials from being exposed in chat logs.`;
1429
1329
  : { status: 'unavailable', reason: completedProvenanceReason },
1430
1330
  sourceTurnId,
1431
1331
  sourceMessageRef,
1332
+ ...(completedOwnerJournalProvenance
1333
+ ? {
1334
+ recoveryProvenance: {
1335
+ status: 'unavailable',
1336
+ reason: 'journal_commit_failed',
1337
+ },
1338
+ }
1339
+ : {}),
1432
1340
  };
1433
1341
  }
1434
1342
  finally {
@@ -1572,22 +1480,6 @@ ${historyContext}
1572
1480
  }
1573
1481
  return this.projectPrivatePromptText(prompt);
1574
1482
  }
1575
- /** TG-05 Decision 7: exact receipt history travels as a separate budget layer. */
1576
- buildOwnerReportHistoryPrompt(session) {
1577
- if (!this.ownerReportInbox || session.source !== 'telegram') {
1578
- return '';
1579
- }
1580
- const restoredTurns = this.sessionStore
1581
- .getHistory(session.id)
1582
- .filter((turn) => turn.state !== 'provisional')
1583
- .slice(-5);
1584
- const reportHistory = this.ownerReportInbox.historyBlock({ source: 'telegram', channelId: session.channelId }, restoredTurns);
1585
- if (!reportHistory) {
1586
- return '';
1587
- }
1588
- logger.info(`Prepared ${reportHistory.length} chars of owner-report history (new session)`);
1589
- return `## Previously Consumed Owner Reports (reference only)\n${reportHistory}`;
1590
- }
1591
1483
  buildSessionPolicyFingerprint(agentContext, enhanced, model, trelloAvailable) {
1592
1484
  const baseInstructions = (0, agent_loop_js_1.loadComposedSystemPrompt)(false, agentContext);
1593
1485
  const surface = (0, private_connector_policy_js_1.resolvePrivatePrincipalSurface)({ agentContext });
@@ -1761,145 +1653,9 @@ ${historyContext}
1761
1653
  updateChannelName(source, channelId, channelName) {
1762
1654
  return this.sessionStore.updateChannelName(source, channelId, channelName);
1763
1655
  }
1764
- /**
1765
- * Trigger memory agent to extract facts (fire-and-forget).
1766
- * Uses AgentProcessManager persistent process.
1767
- */
1768
- static EXTRACT_COOLDOWN_MS = 30_000;
1769
- static MIN_CONTENT_LENGTH = 100;
1770
- static MAX_CONTENT_LENGTH = 10_000;
1771
1656
  getRuntimeProjectId() {
1772
1657
  return process.env.MAMA_WORKSPACE || process.cwd();
1773
1658
  }
1774
- buildMemoryAuditPrompt(job) {
1775
- const escapeBackticks = (s) => s.replace(/```/g, '\\`\\`\\`');
1776
- const scopeContext = job.scopeContext.map((scope) => `${scope.kind}:${scope.id}`).join(', ');
1777
- const candidateLines = (job.candidates ?? []).map((candidate) => {
1778
- const topic = candidate.topicHint ? ` topic=${escapeBackticks(candidate.topicHint)}` : '';
1779
- return `- kind=${candidate.kind}${topic} confidence=${candidate.confidence} summary=${JSON.stringify(candidate.summary)}`;
1780
- });
1781
- const safeConversation = escapeBackticks(job.conversation);
1782
- return `Memory scopes: ${scopeContext}
1783
- Conversation:
1784
- \`\`\`conversation
1785
- ${safeConversation}
1786
- \`\`\`
1787
-
1788
- Candidates:
1789
- ${candidateLines.length > 0 ? candidateLines.join('\n') : '- none'}
1790
-
1791
- INSTRUCTION:
1792
- - Call mama_search exactly once first.
1793
- - If the conversation contains a decision, preference, fact, lesson, or superseding update, call mama_save exactly once.
1794
- - If nothing should be saved, do not call mama_save.
1795
- - Do not call resource discovery tools.
1796
- - Do not ask follow-up questions.
1797
- - After tool work finishes, respond with exactly DONE or SKIP.`;
1798
- }
1799
- classifyMemoryAuditResponse(response) {
1800
- const normalized = response.trim().toLowerCase();
1801
- if (normalized.length === 0) {
1802
- return {
1803
- status: 'skipped',
1804
- action: 'no_op',
1805
- event_ids: [],
1806
- reason: 'memory agent returned an empty response',
1807
- };
1808
- }
1809
- if (normalized.includes('nothing worth saving') ||
1810
- normalized.includes('nothing is worth saving') ||
1811
- normalized.includes('no-op') ||
1812
- normalized.includes('no op') ||
1813
- normalized.includes('skip')) {
1814
- return {
1815
- status: 'skipped',
1816
- action: 'no_op',
1817
- event_ids: [],
1818
- reason: response,
1819
- };
1820
- }
1821
- if (normalized === 'failed' || normalized.includes('failed')) {
1822
- return {
1823
- status: 'failed',
1824
- action: 'no_op',
1825
- event_ids: [],
1826
- reason: response,
1827
- };
1828
- }
1829
- if (normalized === 'done' || normalized === 'applied') {
1830
- return {
1831
- status: 'applied',
1832
- action: 'save',
1833
- event_ids: [],
1834
- reason: response,
1835
- };
1836
- }
1837
- return {
1838
- status: 'skipped',
1839
- action: 'no_op',
1840
- event_ids: [],
1841
- reason: response,
1842
- };
1843
- }
1844
- recordMemoryAuditAck(ack, topic, channelKey, displayTopic, deltaKey) {
1845
- const timestamp = Date.now();
1846
- if (ack.status === 'applied') {
1847
- this.memoryAgentStats.acksApplied++;
1848
- this.memoryAgentStats.factsSaved++;
1849
- // Fire-and-forget save confirmation to originating channel
1850
- if (this.gatewayRegistry && channelKey) {
1851
- const [source, ...channelParts] = channelKey.split(':');
1852
- const channelId = channelParts.join(':');
1853
- const confirmMsg = `✅ Memory saved: ${displayTopic}`;
1854
- this.gatewayRegistry.sendMessage(source, channelId, confirmMsg).catch((err) => {
1855
- logger.warn(`[memory-feedback] Failed to send confirmation: ${err instanceof Error ? err.message : String(err)}`);
1856
- });
1857
- }
1858
- }
1859
- else if (ack.status === 'skipped') {
1860
- this.memoryAgentStats.acksSkipped++;
1861
- }
1862
- else {
1863
- this.memoryAgentStats.acksFailed++;
1864
- logger.warn(`[memory-agent] Audit failed: ${ack.reason ?? 'unknown failure'}`);
1865
- this.memoryNoticeQueue.enqueue(channelKey ?? 'memory-agent:shared', {
1866
- type: 'memory_warning',
1867
- severity: 'high',
1868
- summary: 'memory audit failed',
1869
- evidence: [],
1870
- recommended_action: 'consult_memory',
1871
- relevant_memories: [],
1872
- });
1873
- }
1874
- this.memoryAgentStats.recentExtractions.unshift({
1875
- topic,
1876
- timestamp,
1877
- channelKey,
1878
- status: ack.status,
1879
- });
1880
- this.memoryAgentStats.recentExtractions = this.memoryAgentStats.recentExtractions.slice(0, 10);
1881
- this.memoryAgentStats.lastExtraction = timestamp;
1882
- if (ack.status === 'applied' && channelKey && this.mamaApi.upsertChannelSummary) {
1883
- const summaryMarkdown = [
1884
- '## Channel Summary',
1885
- `- Last memory update: ${displayTopic ?? topic}`,
1886
- `- Status: ${ack.action}`,
1887
- ack.reason ? `- Notes: ${ack.reason.slice(0, 240)}` : '',
1888
- ]
1889
- .filter(Boolean)
1890
- .join('\n');
1891
- void this.mamaApi
1892
- .upsertChannelSummary({
1893
- channelKey,
1894
- summaryMarkdown,
1895
- deltaHash: `${deltaKey ?? topic}:${ack.action}`,
1896
- })
1897
- .catch((error) => {
1898
- logger.warn(`[channel-summary] Failed: ${error instanceof Error ? error.message : String(error)}`);
1899
- });
1900
- }
1901
- }
1902
- /** Classify a committed owner message and save a policy:/lesson: row when it carries one. */
1903
1659
  async observeOwnerLearning(message) {
1904
1660
  if (!this.mamaApi.saveMemory || !this.mamaApi.queryRelevantTruth) {
1905
1661
  return;
@@ -1992,131 +1748,6 @@ INSTRUCTION:
1992
1748
  }
1993
1749
  return `[MAMA Memory]\n${context.prompt}\n[/MAMA Memory]\n\n`;
1994
1750
  }
1995
- async triggerMemoryAgent(channelKey, userText, botResponse, message, sourceTurnId, _sourceMessageRef, parentModelRunId) {
1996
- const memoryAuditQueue = this.memoryAuditQueue;
1997
- if (!this.memoryAgentProcessManager || !memoryAuditQueue) {
1998
- return;
1999
- }
2000
- const now = Date.now();
2001
- const source = message?.source ?? 'memory-agent';
2002
- const channelId = message?.channelId ?? 'shared';
2003
- const userId = message?.userId;
2004
- const candidates = (0, save_candidate_extractor_js_1.extractSaveCandidates)({
2005
- userText,
2006
- gatewayWrapped: message?.metadata?.untrustedWrapped === true,
2007
- botResponse,
2008
- channelKey,
2009
- source,
2010
- channelId,
2011
- userId,
2012
- projectId: this.getRuntimeProjectId(),
2013
- createdAt: now,
2014
- });
2015
- const cooldownKey = `${source}:${channelId}:${userId ?? 'anonymous'}`;
2016
- const lastExtractTime = this.memoryAuditCooldowns.get(cooldownKey) ?? 0;
2017
- // The cooldown rate-limits memory-agent invocations for ORDINARY chatter.
2018
- // Candidate-bearing turns are owner directives/decisions - discarding one
2019
- // because another arrived within 30s loses real instructions, so they
2020
- // bypass the cooldown (bounded by how fast an owner can type directives).
2021
- if (candidates.length === 0 && now - lastExtractTime < MessageRouter.EXTRACT_COOLDOWN_MS) {
2022
- return;
2023
- }
2024
- let content = `User: ${userText}\nAssistant: ${botResponse}`;
2025
- if (content.length < MessageRouter.MIN_CONTENT_LENGTH && candidates.length === 0) {
2026
- return;
2027
- }
2028
- if (content.length > MessageRouter.MAX_CONTENT_LENGTH) {
2029
- content = content.substring(0, MessageRouter.MAX_CONTENT_LENGTH);
2030
- }
2031
- if (candidates.length === 0)
2032
- return;
2033
- this.memoryAuditCooldowns.set(cooldownKey, now);
2034
- // Evict stale cooldown entries (older than 2x cooldown window)
2035
- if (this.memoryAuditCooldowns.size > 100) {
2036
- const staleThreshold = now - MessageRouter.EXTRACT_COOLDOWN_MS * 2;
2037
- for (const [key, ts] of this.memoryAuditCooldowns) {
2038
- if (ts < staleThreshold)
2039
- this.memoryAuditCooldowns.delete(key);
2040
- }
2041
- }
2042
- this.memoryAgentStats.turnsObserved++;
2043
- this.memoryAgentStats.candidatesDetected += candidates.length;
2044
- this.memoryAgentStats.factsExtracted += candidates.length;
2045
- const scopes = (0, scope_context_js_1.deriveMemoryScopes)({
2046
- source,
2047
- channelId,
2048
- userId,
2049
- projectId: this.getRuntimeProjectId(),
2050
- });
2051
- const job = {
2052
- turnId: sourceTurnId ?? `turn_${now}`,
2053
- channelKey,
2054
- source,
2055
- channelId,
2056
- userId,
2057
- parentModelRunId,
2058
- scopeContext: scopes,
2059
- conversation: content,
2060
- candidates,
2061
- };
2062
- const rawTopic = userText.slice(0, 40).toLowerCase();
2063
- const asciiTopic = rawTopic.replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
2064
- const topic = asciiTopic || `topic_${(0, node_crypto_1.createHash)('sha256').update(rawTopic).digest('hex').slice(0, 8)}`;
2065
- const displayTopic = candidates[0]?.topicHint || userText.slice(0, 80).trim() || 'memory_audit';
2066
- const deltaKeySource = candidates[0]?.id || userText;
2067
- const deltaKey = (0, node_crypto_1.createHash)('sha256').update(deltaKeySource).digest('hex').slice(0, 16);
2068
- const memoryStart = Date.now();
2069
- // Start validation session for memory agent
2070
- let memValSession = null;
2071
- if (this.validationService && this.sessionsDb) {
2072
- try {
2073
- const ver = (0, agent_store_js_1.getLatestVersion)(this.sessionsDb, 'memory');
2074
- memValSession = this.validationService.startSession('memory', ver?.version ?? 0, 'audit', {
2075
- goal: `Extract: ${displayTopic.slice(0, 100)}`,
2076
- });
2077
- }
2078
- catch {
2079
- /* non-fatal */
2080
- }
2081
- }
2082
- void (async () => {
2083
- try {
2084
- const ack = await memoryAuditQueue.enqueue(job);
2085
- this.recordMemoryAuditAck(ack, topic, channelKey, displayTopic, deltaKey);
2086
- const dur = Date.now() - memoryStart;
2087
- this.logAgentActivity('memory', 'task_complete', displayTopic.slice(0, 200), undefined, dur);
2088
- if (memValSession && this.validationService) {
2089
- try {
2090
- this.validationService.finalizeSession(memValSession.id, {
2091
- execution_status: 'completed',
2092
- metrics: { duration_ms: dur },
2093
- });
2094
- }
2095
- catch {
2096
- /* non-fatal */
2097
- }
2098
- }
2099
- }
2100
- catch (err) {
2101
- this.memoryAgentStats.acksFailed++;
2102
- const dur = Date.now() - memoryStart;
2103
- this.logAgentActivity('memory', 'task_error', displayTopic.slice(0, 200), undefined, dur, err instanceof Error ? err.message : String(err));
2104
- if (memValSession && this.validationService) {
2105
- try {
2106
- this.validationService.finalizeSession(memValSession.id, {
2107
- execution_status: 'failed',
2108
- error_message: err instanceof Error ? err.message : String(err),
2109
- metrics: { duration_ms: dur },
2110
- });
2111
- }
2112
- catch {
2113
- /* non-fatal */
2114
- }
2115
- }
2116
- logger.warn(`[memory-agent] Failed: ${err instanceof Error ? err.message : String(err)}`);
2117
- }
2118
- })();
2119
- }
2120
1751
  // ── Activity Logging (shared by MAMA frontdoor + memory agent) ──────
2121
1752
  resolveFrontdoorAgentId(message) {
2122
1753
  return message.source === 'viewer' ? 'os-agent' : 'mama';