@jungjaehoon/mama-os 0.48.3 → 0.49.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 (161) 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 +93 -79
  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 +2 -10
  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 +2 -8
  22. package/dist/agent/tool-registry.js.map +1 -1
  23. package/dist/agent/types.d.ts +18 -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 +181 -188
  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 +28 -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 +13 -71
  70. package/dist/gateways/message-router.d.ts.map +1 -1
  71. package/dist/gateways/message-router.js +99 -489
  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 +8 -7
  88. package/dist/operator/owner-event-loop.d.ts.map +1 -1
  89. package/dist/operator/owner-event-loop.js +7 -20
  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 +25 -37
  117. package/dist/operator/situation-report.js.map +1 -1
  118. package/dist/operator/trigger-author.d.ts +0 -45
  119. package/dist/operator/trigger-author.d.ts.map +1 -1
  120. package/dist/operator/trigger-author.js +0 -122
  121. package/dist/operator/trigger-author.js.map +1 -1
  122. package/dist/operator/worker-run.d.ts +9 -23
  123. package/dist/operator/worker-run.d.ts.map +1 -1
  124. package/dist/operator/worker-run.js +18 -74
  125. package/dist/operator/worker-run.js.map +1 -1
  126. package/dist/operator/workorder-consumer.d.ts.map +1 -1
  127. package/dist/operator/workorder-consumer.js +7 -0
  128. package/dist/operator/workorder-consumer.js.map +1 -1
  129. package/dist/scheduler/cron-worker.d.ts +0 -3
  130. package/dist/scheduler/cron-worker.d.ts.map +1 -1
  131. package/dist/scheduler/cron-worker.js +2 -13
  132. package/dist/scheduler/cron-worker.js.map +1 -1
  133. package/dist/scheduler/heartbeat.d.ts +3 -1
  134. package/dist/scheduler/heartbeat.d.ts.map +1 -1
  135. package/dist/scheduler/heartbeat.js +13 -4
  136. package/dist/scheduler/heartbeat.js.map +1 -1
  137. package/dist/validation/types.d.ts +1 -1
  138. package/dist/validation/types.js +1 -1
  139. package/dist/wiki/wiki-turn-contract.d.ts.map +1 -1
  140. package/dist/wiki/wiki-turn-contract.js +1 -0
  141. package/dist/wiki/wiki-turn-contract.js.map +1 -1
  142. package/dist/wiki-artifacts/wiki-publish-adapter.d.ts.map +1 -1
  143. package/dist/wiki-artifacts/wiki-publish-adapter.js +11 -1
  144. package/dist/wiki-artifacts/wiki-publish-adapter.js.map +1 -1
  145. package/package.json +1 -1
  146. package/dist/cli/runtime/memory-agent-init.d.ts +0 -37
  147. package/dist/cli/runtime/memory-agent-init.d.ts.map +0 -1
  148. package/dist/cli/runtime/memory-agent-init.js +0 -202
  149. package/dist/cli/runtime/memory-agent-init.js.map +0 -1
  150. package/dist/gateways/owner-report-inbox.d.ts +0 -40
  151. package/dist/gateways/owner-report-inbox.d.ts.map +0 -1
  152. package/dist/gateways/owner-report-inbox.js +0 -128
  153. package/dist/gateways/owner-report-inbox.js.map +0 -1
  154. package/dist/multi-agent/memory-agent-persona.d.ts +0 -11
  155. package/dist/multi-agent/memory-agent-persona.d.ts.map +0 -1
  156. package/dist/multi-agent/memory-agent-persona.js +0 -175
  157. package/dist/multi-agent/memory-agent-persona.js.map +0 -1
  158. package/dist/operator/report-context.d.ts +0 -184
  159. package/dist/operator/report-context.d.ts.map +0 -1
  160. package/dist/operator/report-context.js +0 -1052
  161. 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,15 @@ 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 initialSession = sessionPool.getSession(runtimeSessionKey);
860
808
  let cliSessionId = initialSession.sessionId;
861
809
  let isNewCliSession = initialSession.isNew;
862
810
  const busy = initialSession.busy;
@@ -864,6 +812,7 @@ This protects your credentials from being exposed in chat logs.`;
864
812
  // assignment sits inside the agent-run block and is not visible at the return.
865
813
  let completedModelRunId = null;
866
814
  let completedProvenanceReason = 'backend_no_run';
815
+ let completedOwnerJournalProvenance;
867
816
  const sourceTurnId = message.metadata?.messageId ?? `generated:${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
868
817
  const sourceMessageRef = [message.source, message.channelId, sourceTurnId]
869
818
  .filter(Boolean)
@@ -882,7 +831,7 @@ This protects your credentials from being exposed in chat logs.`;
882
831
  while (Date.now() - waitStart < maxWaitMs) {
883
832
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
884
833
  // Use read-only peek to avoid side effects (no lock increment)
885
- const check = sessionPool.peekSession(channelKey);
834
+ const check = sessionPool.peekSession(runtimeSessionKey);
886
835
  if (!check.busy) {
887
836
  logger.debug(`Session released for ${channelKey} after ${Math.round((Date.now() - waitStart) / 1000)}s`);
888
837
  break;
@@ -894,7 +843,7 @@ This protects your credentials from being exposed in chat logs.`;
894
843
  }
895
844
  }
896
845
  // Re-acquire session after wait (properly locks it this time)
897
- const reacquired = sessionPool.getSession(channelKey);
846
+ const reacquired = sessionPool.getSession(runtimeSessionKey);
898
847
  if (reacquired.busy) {
899
848
  // Still busy after timeout - we never acquired the lock
900
849
  throw new Error(`Session for ${channelKey} timed out after ${maxWaitMs / 1000}s`);
@@ -907,35 +856,17 @@ This protects your credentials from being exposed in chat logs.`;
907
856
  const releaseCliSessionLock = () => {
908
857
  if (!acquiredLock || lockReleased)
909
858
  return;
910
- sessionPool.releaseSession(channelKey, cliSessionId);
859
+ sessionPool.releaseSession(runtimeSessionKey, cliSessionId);
911
860
  lockReleased = true;
912
861
  };
913
862
  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
863
  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.
864
+ // Save user message immediately for crash/refresh resilience.
934
865
  this.sessionStore.appendMessage(session.id, {
935
866
  role: 'user',
936
867
  content: [message.text, mediaInstructions].filter(Boolean).join('\n\n'),
937
868
  timestamp: Date.now(),
938
- }, ownerReportSnapshot ? { sourceMessageRef } : {});
869
+ });
939
870
  let response = '';
940
871
  let context = { prompt: '', decisions: [], hasContext: false };
941
872
  let streamFlushTimer = null;
@@ -948,7 +879,6 @@ This protects your credentials from being exposed in chat logs.`;
948
879
  // CONTINUE turns: Codex server retains full conversation via threadId,
949
880
  // so skip expensive prompt rebuilding (embedding search, DB history, etc.)
950
881
  let systemPrompt;
951
- let ownerReportHistoryPrompt = '';
952
882
  const historyContext = message.metadata?.historyContext;
953
883
  // Public turns never inspect private workspace rules, skills, or AGENTS files.
954
884
  const enhanced = isPublicLane
@@ -957,7 +887,12 @@ This protects your credentials from being exposed in chat logs.`;
957
887
  // TG-05: every main backend keeps its live conversation in a durable
958
888
  // runtime session. Only a genuinely new/replacement session rebuilds
959
889
  // bounded MAMA context.
960
- const needsFullContext = isNewCliSession;
890
+ // The in-memory SessionPool is empty after a daemon restart, while a
891
+ // durable Codex thread may still be alive. Codex first attempts that
892
+ // thread with a minimal continuation; AgentLoop invokes the lazy full
893
+ // builder only when the registry says the thread is actually missing.
894
+ const lazyDurableResume = this.config.backend === 'codex' && this.agentLoop.probesDurableSession === true;
895
+ const needsFullContext = isNewCliSession && !lazyDurableResume;
961
896
  // Persistent backends skip repeated retrieval after the first turn.
962
897
  context =
963
898
  !isPublicLane && needsFullContext && this.config.implicitLegacyContextSearch
@@ -982,8 +917,8 @@ This protects your credentials from being exposed in chat logs.`;
982
917
  source: message.source,
983
918
  channelId: message.channelId,
984
919
  });
985
- systemPrompt = this.buildSystemPrompt(session, context.prompt, historyContext, sessionStartupContext, agentContext, enhanced, true, trelloAvailable);
986
- ownerReportHistoryPrompt = this.buildOwnerReportHistoryPrompt(session);
920
+ systemPrompt = this.buildSystemPrompt(session, context.prompt, historyContext, sessionStartupContext, agentContext, enhanced, !(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY &&
921
+ this.agentLoop.ownerRecoveryJournalEnabled === true), trelloAvailable);
987
922
  }
988
923
  }
989
924
  // 7. Run agent loop (with session info for lane-based concurrency)
@@ -1019,12 +954,13 @@ This protects your credentials from being exposed in chat logs.`;
1019
954
  // still exist and should be resumed. Keep prompt/context freshness separate
1020
955
  // from backend conversation continuity.
1021
956
  const shouldResumeBackend = this.config.backend === 'codex' ? true : shouldResume;
957
+ const usesContinuationContext = shouldResume || lazyDurableResume;
1022
958
  // For resumed sessions: inject minimal context only
1023
959
  // Persistent CLI keeps the process alive with full system prompt from initial request
1024
960
  // Only inject per-message context (related decisions) to avoid context overflow
1025
961
  const effectivePrompt = isPublicLane
1026
962
  ? publicLaneBaseInstructions
1027
- : shouldResume
963
+ : usesContinuationContext
1028
964
  ? this.buildMinimalResumePrompt(context.prompt, agentContext)
1029
965
  : systemPrompt;
1030
966
  // Wrap stream callbacks to accumulate deltas and periodically flush to DB
@@ -1047,7 +983,6 @@ This protects your credentials from being exposed in chat logs.`;
1047
983
  }
1048
984
  }, streamFlushIntervalMs);
1049
985
  }
1050
- let parentModelRunId;
1051
986
  // Skill on-demand injection: prepend matched skill content to user message
1052
987
  // (not system prompt — PersistentCLI can't update system prompt after creation)
1053
988
  const skillPrefix = !isPublicLane && enhanced.skillContent
@@ -1070,17 +1005,20 @@ This protects your credentials from being exposed in chat logs.`;
1070
1005
  const envelope = this.buildReactiveEnvelope(message, agentContext, memberEffectiveScope);
1071
1006
  const options = {
1072
1007
  systemPrompt: effectivePrompt,
1073
- ...(ownerReportHistoryPrompt ? { ownerReportHistoryPrompt } : {}),
1074
1008
  sessionPolicyFingerprint,
1075
1009
  ...(memberEffectiveScope ? { memberEffectiveScope } : {}),
1076
1010
  ...(memberEffectiveScope ? { memberScopeRequired: true } : {}),
1077
1011
  userId: message.userId,
1078
1012
  model: roleModel, // Role-specific model override
1079
1013
  maxTurns: roleMaxTurns, // Role-specific max turns
1080
- sessionKey: channelKey,
1014
+ sessionKey: runtimeSessionKey,
1015
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY ? { lanePriority: 100 } : {}),
1081
1016
  source: message.source,
1082
1017
  channelId: message.channelId,
1083
1018
  agentContext,
1019
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY
1020
+ ? { sessionPolicyRole: ownerRuntimeRole }
1021
+ : {}),
1084
1022
  resumeSession: shouldResumeBackend,
1085
1023
  cliSessionId, // Pass CLI session ID to avoid double-locking
1086
1024
  // AgentLoop may replace a stale backend session while this request still
@@ -1092,6 +1030,9 @@ This protects your credentials from being exposed in chat logs.`;
1092
1030
  envelope,
1093
1031
  sourceTurnId,
1094
1032
  sourceMessageRef,
1033
+ ...(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY
1034
+ ? { ownerJournalPrompt: message.text, ownerJournalTrust: 'owner' }
1035
+ : {}),
1095
1036
  // The owner's message is what caused whatever this turn changes, and the router
1096
1037
  // knows it BEFORE the agent runs - the same shape as a reconcile work order
1097
1038
  // carrying its delta batch. Without this the chat lane had no cause wire at all:
@@ -1104,7 +1045,7 @@ This protects your credentials from being exposed in chat logs.`;
1104
1045
  // citation this ledger exists to refuse.
1105
1046
  ...(causeFromOwnerMessage(sourceTurnId, sourceMessageRef) ?? {}),
1106
1047
  };
1107
- if (shouldResume) {
1048
+ if (shouldResumeBackend) {
1108
1049
  options.freshSessionSystemPrompt = async () => {
1109
1050
  if (isPublicLane) {
1110
1051
  return memberEffectiveScope
@@ -1118,27 +1059,21 @@ This protects your credentials from being exposed in chat logs.`;
1118
1059
  source: message.source,
1119
1060
  channelId: message.channelId,
1120
1061
  });
1121
- return this.buildSystemPrompt(session, freshContext.prompt, historyContext, sessionStartupContext, agentContext, enhanced, true, trelloAvailable);
1062
+ return this.buildSystemPrompt(session, freshContext.prompt, historyContext, sessionStartupContext, agentContext, enhanced, !(runtimeSessionKey === owner_runtime_js_1.OWNER_RUNTIME_SESSION_KEY &&
1063
+ this.agentLoop.ownerRecoveryJournalEnabled === true), trelloAvailable);
1122
1064
  };
1123
- if (!isPublicLane) {
1124
- options.freshSessionOwnerReportHistoryPrompt = () => this.buildOwnerReportHistoryPrompt(session);
1125
- }
1126
1065
  }
1127
- if (shouldResume) {
1066
+ if (usesContinuationContext) {
1128
1067
  logger.info(`Resuming CLI session (minimal: ${effectivePrompt.length} chars)`);
1129
1068
  }
1130
1069
  else {
1131
1070
  logger.info(`New CLI session (full: ${systemPrompt.length} chars)`);
1132
1071
  }
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
1072
  try {
1138
1073
  if (isPublicLane) {
1139
1074
  memoryPrefix = '';
1140
1075
  }
1141
- else if (shouldResume) {
1076
+ else if (usesContinuationContext) {
1142
1077
  // Per-channel notices PLUS the operator broadcast key: host-code
1143
1078
  // alarms (workorder failures) have no conversation channel at
1144
1079
  // enqueue time - without the broadcast read they dead-letter
@@ -1166,7 +1101,7 @@ This protects your credentials from being exposed in chat logs.`;
1166
1101
  }
1167
1102
  catch (err) {
1168
1103
  logger.warn(`[memory-prefix] Failed: ${err instanceof Error ? err.message : String(err)}`);
1169
- if (!isPublicLane && shouldResume) {
1104
+ if (!isPublicLane && usesContinuationContext) {
1170
1105
  try {
1171
1106
  memoryPrefix = await this.getPerTurnMemoryPrefix(message);
1172
1107
  }
@@ -1212,7 +1147,7 @@ This protects your credentials from being exposed in chat logs.`;
1212
1147
  }
1213
1148
  // Add text content (with memory context, skill context, and page context)
1214
1149
  const pageCtx = isPublicLane ? '' : this.getPageContextPrefix(message);
1215
- const effectiveMessageText = `${pageCtx}${inboxPrefix}${carryPrefix}${memoryPrefix}${learningPrefix}${skillPrefix}${messageText || ''}`;
1150
+ const effectiveMessageText = `${pageCtx}${memoryPrefix}${learningPrefix}${skillPrefix}${messageText || ''}`;
1216
1151
  if (effectiveMessageText) {
1217
1152
  contentBlocks.push({ type: 'text', text: effectiveMessageText });
1218
1153
  }
@@ -1247,48 +1182,27 @@ This protects your credentials from being exposed in chat logs.`;
1247
1182
  const turnStart = Date.now();
1248
1183
  const result = await this.agentLoop.runWithContent(contentBlocks, options);
1249
1184
  response = result.response;
1250
- parentModelRunId = result.modelRunId ?? undefined;
1251
1185
  completedModelRunId = result.modelRunId ?? completedModelRunId;
1252
1186
  if (result.modelRunProvenance === 'commit_failed') {
1253
1187
  completedProvenanceReason = 'commit_failed';
1254
1188
  }
1189
+ completedOwnerJournalProvenance = result.ownerJournalProvenance;
1255
1190
  this.logFrontdoorActivity(message, message.text, response, Date.now() - turnStart);
1256
1191
  }
1257
1192
  else {
1258
1193
  const pageCtx = isPublicLane ? '' : this.getPageContextPrefix(message);
1259
- const effectiveText = `${pageCtx}${inboxPrefix}${carryPrefix}${memoryPrefix}${learningPrefix}${skillPrefix}${message.text}`;
1194
+ const effectiveText = `${pageCtx}${memoryPrefix}${learningPrefix}${skillPrefix}${message.text}`;
1260
1195
  const turnStart = Date.now();
1261
1196
  const result = await this.agentLoop.run(effectiveText, options);
1262
1197
  response = result.response;
1263
- parentModelRunId = result.modelRunId ?? undefined;
1264
1198
  completedModelRunId = result.modelRunId ?? completedModelRunId;
1265
1199
  if (result.modelRunProvenance === 'commit_failed') {
1266
1200
  completedProvenanceReason = 'commit_failed';
1267
1201
  }
1202
+ completedOwnerJournalProvenance = result.ownerJournalProvenance;
1268
1203
  this.logFrontdoorActivity(message, message.text, response, Date.now() - turnStart);
1269
1204
  }
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) {
1205
+ if (usesContinuationContext && pendingNotices) {
1292
1206
  // Drain each queue by ITS OWN peeked count - notices enqueued while
1293
1207
  // the agent run was in flight stay for the next turn (review N2).
1294
1208
  if (pendingChannelNoticeCount > 0) {
@@ -1312,7 +1226,7 @@ This protects your credentials from being exposed in chat logs.`;
1312
1226
  normalizedErrorMsg.includes('exited with code');
1313
1227
  if (isCriticalError) {
1314
1228
  logger.warn(`CLI error detected, invalidating session: ${errorMsg}`);
1315
- sessionPool.invalidateSession(channelKey, cliSessionId);
1229
+ sessionPool.invalidateSession(runtimeSessionKey, cliSessionId);
1316
1230
  }
1317
1231
  // Release session lock before re-throwing
1318
1232
  releaseCliSessionLock();
@@ -1358,36 +1272,14 @@ This protects your credentials from being exposed in chat logs.`;
1358
1272
  });
1359
1273
  }
1360
1274
  // 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
- },
1275
+ const persisted = this.sessionStore.flushStreamingResponse(session.id, response) ||
1276
+ this.sessionStore.appendMessage(session.id, {
1277
+ role: 'assistant',
1278
+ content: response,
1279
+ timestamp: Date.now(),
1377
1280
  });
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
- }
1281
+ if (!persisted) {
1282
+ throw new Error('Unable to persist final assistant response');
1391
1283
  }
1392
1284
  if ((message.principal?.lane ?? 'owner') !== 'public' &&
1393
1285
  response &&
@@ -1400,19 +1292,6 @@ This protects your credentials from being exposed in chat logs.`;
1400
1292
  logger.warn(`[learning] observer failed: ${error instanceof Error ? error.message : String(error)}`);
1401
1293
  });
1402
1294
  }
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
1295
  // Release session lock AFTER final persistence to prevent out-of-order turns
1417
1296
  releaseCliSessionLock();
1418
1297
  // 6. Return result. Run identity rides out with it: the router already generated
@@ -1429,6 +1308,14 @@ This protects your credentials from being exposed in chat logs.`;
1429
1308
  : { status: 'unavailable', reason: completedProvenanceReason },
1430
1309
  sourceTurnId,
1431
1310
  sourceMessageRef,
1311
+ ...(completedOwnerJournalProvenance
1312
+ ? {
1313
+ recoveryProvenance: {
1314
+ status: 'unavailable',
1315
+ reason: 'journal_commit_failed',
1316
+ },
1317
+ }
1318
+ : {}),
1432
1319
  };
1433
1320
  }
1434
1321
  finally {
@@ -1572,22 +1459,6 @@ ${historyContext}
1572
1459
  }
1573
1460
  return this.projectPrivatePromptText(prompt);
1574
1461
  }
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
1462
  buildSessionPolicyFingerprint(agentContext, enhanced, model, trelloAvailable) {
1592
1463
  const baseInstructions = (0, agent_loop_js_1.loadComposedSystemPrompt)(false, agentContext);
1593
1464
  const surface = (0, private_connector_policy_js_1.resolvePrivatePrincipalSurface)({ agentContext });
@@ -1761,145 +1632,9 @@ ${historyContext}
1761
1632
  updateChannelName(source, channelId, channelName) {
1762
1633
  return this.sessionStore.updateChannelName(source, channelId, channelName);
1763
1634
  }
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
1635
  getRuntimeProjectId() {
1772
1636
  return process.env.MAMA_WORKSPACE || process.cwd();
1773
1637
  }
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
1638
  async observeOwnerLearning(message) {
1904
1639
  if (!this.mamaApi.saveMemory || !this.mamaApi.queryRelevantTruth) {
1905
1640
  return;
@@ -1992,131 +1727,6 @@ INSTRUCTION:
1992
1727
  }
1993
1728
  return `[MAMA Memory]\n${context.prompt}\n[/MAMA Memory]\n\n`;
1994
1729
  }
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
1730
  // ── Activity Logging (shared by MAMA frontdoor + memory agent) ──────
2121
1731
  resolveFrontdoorAgentId(message) {
2122
1732
  return message.source === 'viewer' ? 'os-agent' : 'mama';