@jungjaehoon/mama-os 0.36.0 → 0.37.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 (158) hide show
  1. package/dist/agent/agent-loop.d.ts +2 -1
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +17 -12
  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 +27 -2
  7. package/dist/agent/code-act/host-bridge.js.map +1 -1
  8. package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -1
  9. package/dist/agent/code-act/type-definition-generator.js +3 -1
  10. package/dist/agent/code-act/type-definition-generator.js.map +1 -1
  11. package/dist/agent/context-compile-service.d.ts +2 -0
  12. package/dist/agent/context-compile-service.d.ts.map +1 -1
  13. package/dist/agent/context-compile-service.js +1 -1
  14. package/dist/agent/context-compile-service.js.map +1 -1
  15. package/dist/agent/drive-tools.d.ts +27 -1
  16. package/dist/agent/drive-tools.d.ts.map +1 -1
  17. package/dist/agent/drive-tools.js +68 -2
  18. package/dist/agent/drive-tools.js.map +1 -1
  19. package/dist/agent/gateway-tool-executor.d.ts +11 -6
  20. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  21. package/dist/agent/gateway-tool-executor.js +210 -37
  22. package/dist/agent/gateway-tool-executor.js.map +1 -1
  23. package/dist/agent/gateway-tools.md +2 -2
  24. package/dist/agent/tool-registry.js +2 -2
  25. package/dist/agent/tool-registry.js.map +1 -1
  26. package/dist/agent/types.d.ts +15 -0
  27. package/dist/agent/types.d.ts.map +1 -1
  28. package/dist/agent/types.js.map +1 -1
  29. package/dist/api/report-handler.d.ts +9 -5
  30. package/dist/api/report-handler.d.ts.map +1 -1
  31. package/dist/api/report-handler.js +26 -13
  32. package/dist/api/report-handler.js.map +1 -1
  33. package/dist/api/report-persistence.d.ts.map +1 -1
  34. package/dist/api/report-persistence.js +6 -3
  35. package/dist/api/report-persistence.js.map +1 -1
  36. package/dist/api/runtime-status-handler.d.ts +1 -0
  37. package/dist/api/runtime-status-handler.d.ts.map +1 -1
  38. package/dist/api/runtime-status-handler.js +1 -0
  39. package/dist/api/runtime-status-handler.js.map +1 -1
  40. package/dist/cli/commands/start.d.ts +14 -20
  41. package/dist/cli/commands/start.d.ts.map +1 -1
  42. package/dist/cli/commands/start.js +207 -248
  43. package/dist/cli/commands/start.js.map +1 -1
  44. package/dist/cli/commands/status.d.ts +6 -0
  45. package/dist/cli/commands/status.d.ts.map +1 -1
  46. package/dist/cli/commands/status.js +89 -0
  47. package/dist/cli/commands/status.js.map +1 -1
  48. package/dist/cli/config/types.d.ts +0 -21
  49. package/dist/cli/config/types.d.ts.map +1 -1
  50. package/dist/cli/config/types.js +0 -7
  51. package/dist/cli/config/types.js.map +1 -1
  52. package/dist/cli/index.js +2 -15
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/cli/runtime/agent-loop-init.d.ts +2 -2
  55. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  56. package/dist/cli/runtime/agent-loop-init.js +3 -3
  57. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  58. package/dist/cli/runtime/api-routes-init.d.ts +7 -1
  59. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  60. package/dist/cli/runtime/api-routes-init.js +99 -38
  61. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  62. package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
  63. package/dist/cli/runtime/api-server-init.js +3 -0
  64. package/dist/cli/runtime/api-server-init.js.map +1 -1
  65. package/dist/gateways/message-router.js +10 -10
  66. package/dist/gateways/message-router.js.map +1 -1
  67. package/dist/gateways/telegram.d.ts +2 -2
  68. package/dist/gateways/telegram.d.ts.map +1 -1
  69. package/dist/gateways/telegram.js +13 -7
  70. package/dist/gateways/telegram.js.map +1 -1
  71. package/dist/multi-agent/agent-event-bus.d.ts +0 -5
  72. package/dist/multi-agent/agent-event-bus.d.ts.map +1 -1
  73. package/dist/multi-agent/agent-event-bus.js.map +1 -1
  74. package/dist/operator/action-verifier.d.ts +2 -0
  75. package/dist/operator/action-verifier.d.ts.map +1 -1
  76. package/dist/operator/action-verifier.js +3 -2
  77. package/dist/operator/action-verifier.js.map +1 -1
  78. package/dist/operator/board-reconcile.d.ts +2 -2
  79. package/dist/operator/board-reconcile.d.ts.map +1 -1
  80. package/dist/operator/board-reconcile.js +10 -2
  81. package/dist/operator/board-reconcile.js.map +1 -1
  82. package/dist/operator/board-refresh-gate.d.ts +34 -0
  83. package/dist/operator/board-refresh-gate.d.ts.map +1 -0
  84. package/dist/operator/board-refresh-gate.js +82 -0
  85. package/dist/operator/board-refresh-gate.js.map +1 -0
  86. package/dist/operator/briefs.d.ts.map +1 -1
  87. package/dist/operator/briefs.js +92 -4
  88. package/dist/operator/briefs.js.map +1 -1
  89. package/dist/operator/operator-trigger-loop.d.ts +7 -22
  90. package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
  91. package/dist/operator/operator-trigger-loop.js +33 -34
  92. package/dist/operator/operator-trigger-loop.js.map +1 -1
  93. package/dist/operator/owner-event-effects.d.ts +34 -0
  94. package/dist/operator/owner-event-effects.d.ts.map +1 -0
  95. package/dist/operator/owner-event-effects.js +131 -0
  96. package/dist/operator/owner-event-effects.js.map +1 -0
  97. package/dist/operator/{conductor-inbox.d.ts → owner-event-inbox.d.ts} +23 -12
  98. package/dist/operator/owner-event-inbox.d.ts.map +1 -0
  99. package/dist/operator/{conductor-inbox.js → owner-event-inbox.js} +97 -40
  100. package/dist/operator/owner-event-inbox.js.map +1 -0
  101. package/dist/operator/owner-event-loop.d.ts +59 -0
  102. package/dist/operator/owner-event-loop.d.ts.map +1 -0
  103. package/dist/operator/owner-event-loop.js +141 -0
  104. package/dist/operator/owner-event-loop.js.map +1 -0
  105. package/dist/operator/owner-event-outcome.d.ts +30 -0
  106. package/dist/operator/owner-event-outcome.d.ts.map +1 -0
  107. package/dist/operator/owner-event-outcome.js +108 -0
  108. package/dist/operator/owner-event-outcome.js.map +1 -0
  109. package/dist/operator/owner-event-policy.d.ts +20 -0
  110. package/dist/operator/owner-event-policy.d.ts.map +1 -0
  111. package/dist/operator/owner-event-policy.js +50 -0
  112. package/dist/operator/owner-event-policy.js.map +1 -0
  113. package/dist/operator/owner-event-prompt.d.ts +10 -0
  114. package/dist/operator/owner-event-prompt.d.ts.map +1 -0
  115. package/dist/operator/owner-event-prompt.js +63 -0
  116. package/dist/operator/owner-event-prompt.js.map +1 -0
  117. package/dist/operator/task-ledger.d.ts +2 -0
  118. package/dist/operator/task-ledger.d.ts.map +1 -1
  119. package/dist/operator/task-ledger.js +12 -1
  120. package/dist/operator/task-ledger.js.map +1 -1
  121. package/dist/operator/trigger-matcher.d.ts.map +1 -1
  122. package/dist/operator/trigger-matcher.js +4 -1
  123. package/dist/operator/trigger-matcher.js.map +1 -1
  124. package/dist/operator/trigger-registry.d.ts +1 -1
  125. package/dist/operator/trigger-registry.d.ts.map +1 -1
  126. package/dist/operator/trigger-registry.js +2 -2
  127. package/dist/operator/trigger-registry.js.map +1 -1
  128. package/dist/operator/trigger-types.d.ts +2 -0
  129. package/dist/operator/trigger-types.d.ts.map +1 -1
  130. package/dist/operator/workorder-hooks.d.ts +14 -0
  131. package/dist/operator/workorder-hooks.d.ts.map +1 -1
  132. package/dist/operator/workorder-hooks.js +64 -0
  133. package/dist/operator/workorder-hooks.js.map +1 -1
  134. package/dist/operator/workorder-publishers.d.ts +14 -4
  135. package/dist/operator/workorder-publishers.d.ts.map +1 -1
  136. package/dist/operator/workorder-publishers.js +51 -13
  137. package/dist/operator/workorder-publishers.js.map +1 -1
  138. package/dist/package-version.d.ts +3 -0
  139. package/dist/package-version.d.ts.map +1 -0
  140. package/dist/package-version.js +18 -0
  141. package/dist/package-version.js.map +1 -0
  142. package/package.json +2 -2
  143. package/public/viewer/js/modules/system.js +1 -0
  144. package/public/viewer/src/modules/system.ts +2 -0
  145. package/dist/operator/board-reground.d.ts +0 -19
  146. package/dist/operator/board-reground.d.ts.map +0 -1
  147. package/dist/operator/board-reground.js +0 -31
  148. package/dist/operator/board-reground.js.map +0 -1
  149. package/dist/operator/conductor-inbox.d.ts.map +0 -1
  150. package/dist/operator/conductor-inbox.js.map +0 -1
  151. package/dist/operator/conductor-session.d.ts +0 -55
  152. package/dist/operator/conductor-session.d.ts.map +0 -1
  153. package/dist/operator/conductor-session.js +0 -110
  154. package/dist/operator/conductor-session.js.map +0 -1
  155. package/dist/operator/conductor.d.ts +0 -69
  156. package/dist/operator/conductor.d.ts.map +0 -1
  157. package/dist/operator/conductor.js +0 -90
  158. package/dist/operator/conductor.js.map +0 -1
@@ -85,6 +85,9 @@ const DEFAULT_PRIVATE_CONNECTOR_POLICY = (0, private_connector_policy_js_1.resol
85
85
  config: {},
86
86
  enabledNames: [],
87
87
  });
88
+ function isReportPublishResult(value) {
89
+ return value !== undefined && !Array.isArray(value);
90
+ }
88
91
  function serializeTaskToolRecord(task) {
89
92
  return {
90
93
  ...task,
@@ -404,12 +407,16 @@ class GatewayToolExecutor {
404
407
  this.obsidianVaultName = vaultName ?? null;
405
408
  }
406
409
  taskLedger = null;
410
+ ownerEventEffectLedger = null;
407
411
  setTaskLedger(ledger) {
408
412
  this.taskLedger = ledger;
409
413
  }
410
414
  getTaskLedger() {
411
415
  return this.taskLedger;
412
416
  }
417
+ setOwnerEventEffectLedger(ledger) {
418
+ this.ownerEventEffectLedger = ledger;
419
+ }
413
420
  agentEventBus = null;
414
421
  setAgentEventBus(bus) {
415
422
  this.agentEventBus = bus;
@@ -457,6 +464,7 @@ class GatewayToolExecutor {
457
464
  workorderAttemptId: executionContext?.workorderAttemptId,
458
465
  temporalWorkContext: executionContext?.temporalWorkContext,
459
466
  causeEventIds: executionContext?.causeEventIds,
467
+ ownerEventEffects: executionContext?.ownerEventEffects,
460
468
  signal: executionContext?.signal,
461
469
  parentToolName: executionContext?.parentToolName,
462
470
  backgroundTasks: executionContext?.backgroundTasks,
@@ -495,9 +503,12 @@ class GatewayToolExecutor {
495
503
  // Never merged from fallback - temporal authority belongs to one claimed run only.
496
504
  temporalWorkContext: active.temporalWorkContext,
497
505
  causeEventIds: active.causeEventIds,
506
+ ownerEventEffects: active.ownerEventEffects,
498
507
  signal: active.signal,
499
508
  parentToolName: active.parentToolName ?? fallback.parentToolName,
500
509
  backgroundTasks: active.backgroundTasks ?? fallback.backgroundTasks,
510
+ // Never merged from fallback - one snapshot belongs to one gateway call only.
511
+ channelGrantSnapshot: active.channelGrantSnapshot,
501
512
  // Never merged from fallback - blocks are strictly per-call.
502
513
  disallowedGatewayTools: active.disallowedGatewayTools,
503
514
  };
@@ -851,7 +862,7 @@ class GatewayToolExecutor {
851
862
  }
852
863
  try {
853
864
  this.envelopeEnforcer.check(ctx.envelope, toolName, enforcementInput, {
854
- readScopeMirror: this.readScopeMirrorFor(toolName, ctx.envelope),
865
+ readScopeMirror: this.readScopeMirrorFor(toolName, ctx.envelope, ctx.channelGrantSnapshot),
855
866
  });
856
867
  return undefined;
857
868
  }
@@ -951,7 +962,10 @@ class GatewayToolExecutor {
951
962
  const baseCtx = this.mergeWithFallbackExecutionContext(this.executionContextStorage.getStore());
952
963
  baseCtx.signal?.throwIfAborted();
953
964
  const gatewayCallId = baseCtx.gatewayCallId ?? `gw_${(0, crypto_1.randomUUID)().replace(/-/g, '')}`;
954
- const ctx = { ...baseCtx, gatewayCallId };
965
+ const channelGrantSnapshot = baseCtx.envelope && index_js_1.MIRROR_READABLE_TOOLS.has(toolName)
966
+ ? snapshotChannelGrant(this.channelGrantProvider)
967
+ : undefined;
968
+ const ctx = { ...baseCtx, gatewayCallId, channelGrantSnapshot };
955
969
  const effectiveInput = this.applyEnvelopeScopedReadDefaults(toolName, input, ctx);
956
970
  const computedScopeAudit = this.computeScopeAuditFields(toolName, effectiveInput, ctx);
957
971
  const scopeAudit = ctx.temporalWorkContext
@@ -1008,7 +1022,7 @@ class GatewayToolExecutor {
1008
1022
  await this.failDirectModelRunIfNeeded(traceState, toolName, auditError).catch((finalizationError) => {
1009
1023
  securityLogger.warn('[model-run] failed to finalize failed direct model run', finalizationError);
1010
1024
  });
1011
- this.logGatewayToolCall(activeCtx, toolName, undefined, Date.now() - startedAt, scopeAudit, gatewayCallId, auditError);
1025
+ this.logGatewayToolCall(activeCtx, toolName, undefined, Date.now() - startedAt, scopeAudit, gatewayCallId, this.gatewayTraceDetails(toolName, undefined), auditError);
1012
1026
  if (scopeAudit.mismatch) {
1013
1027
  this.alarmScopeMismatch(activeCtx, toolName);
1014
1028
  }
@@ -1023,13 +1037,13 @@ class GatewayToolExecutor {
1023
1037
  }
1024
1038
  }
1025
1039
  catch (postRunError) {
1026
- this.logGatewayToolCall(activeCtx, toolName, auditResult, Date.now() - startedAt, scopeAudit, gatewayCallId, postRunError);
1040
+ this.logGatewayToolCall(activeCtx, toolName, auditResult, Date.now() - startedAt, scopeAudit, gatewayCallId, this.gatewayTraceDetails(toolName, auditResult), postRunError);
1027
1041
  if (scopeAudit.mismatch) {
1028
1042
  this.alarmScopeMismatch(activeCtx, toolName);
1029
1043
  }
1030
1044
  throw postRunError;
1031
1045
  }
1032
- this.logGatewayToolCall(activeCtx, toolName, auditResult, Date.now() - startedAt, scopeAudit, gatewayCallId);
1046
+ this.logGatewayToolCall(activeCtx, toolName, auditResult, Date.now() - startedAt, scopeAudit, gatewayCallId, this.gatewayTraceDetails(toolName, auditResult));
1033
1047
  if (scopeAudit.mismatch) {
1034
1048
  this.alarmScopeMismatch(activeCtx, toolName);
1035
1049
  }
@@ -1187,7 +1201,9 @@ class GatewayToolExecutor {
1187
1201
  return { requestedScopes, envelopeScopesSnapshot, mismatch: 0 };
1188
1202
  }
1189
1203
  if (!requestedScopes || requestedScopes.length === 0) {
1190
- if (toolName === 'context_compile' && Array.isArray(input.scopes)) {
1204
+ const requestedScopeValue = input.scopes;
1205
+ if (toolName === 'context_compile' &&
1206
+ (requestedScopeValue === undefined || Array.isArray(requestedScopeValue))) {
1191
1207
  return { requestedScopes, envelopeScopesSnapshot, mismatch: 0 };
1192
1208
  }
1193
1209
  return {
@@ -1196,7 +1212,11 @@ class GatewayToolExecutor {
1196
1212
  mismatch: envelopeScopesSnapshot.length > 0 ? 1 : 0,
1197
1213
  };
1198
1214
  }
1199
- const envelopeScopeKeys = new Set(envelopeScopesSnapshot.map(memoryScopeKey));
1215
+ const effectiveAuditAllowance = [
1216
+ ...envelopeScopesSnapshot,
1217
+ ...(this.readScopeMirrorFor(toolName, ctx.envelope, ctx.channelGrantSnapshot) ?? []),
1218
+ ];
1219
+ const envelopeScopeKeys = new Set(effectiveAuditAllowance.map(memoryScopeKey));
1200
1220
  const hasOutOfEnvelopeScope = requestedScopes.some((scope) => !envelopeScopeKeys.has(memoryScopeKey(scope)));
1201
1221
  return {
1202
1222
  requestedScopes,
@@ -1243,7 +1263,7 @@ class GatewayToolExecutor {
1243
1263
  const envelopeScopes = ctx.envelope
1244
1264
  ? [
1245
1265
  ...ctx.envelope.scope.memory_scopes,
1246
- ...(0, read_js_1.mirrorReadScopes)(ctx.envelope, this.channelGrantProvider()),
1266
+ ...(this.readScopeMirrorFor(toolName, ctx.envelope, ctx.channelGrantSnapshot) ?? []),
1247
1267
  ]
1248
1268
  : null;
1249
1269
  const allowedScopes = envelopeScopes ?? this.deriveMemoryScopesFromActiveContext(ctx) ?? [];
@@ -1290,15 +1310,14 @@ class GatewayToolExecutor {
1290
1310
  }
1291
1311
  /**
1292
1312
  * The grant-mirror READ allowance for memory tools (see mirrorReadScopes):
1293
- * computed lazily - only memory-scoped tools pay the connector-config read -
1294
- * and against the LIVE grant, so a channel the owner removes stops being
1295
- * readable on the next call, mid-envelope included.
1313
+ * computed from the gateway-call snapshot, so every authority check agrees
1314
+ * within one call while the next call observes a fresh live grant.
1296
1315
  */
1297
- readScopeMirrorFor(toolName, envelope) {
1298
- if (!index_js_1.MIRROR_READABLE_TOOLS.has(toolName)) {
1316
+ readScopeMirrorFor(toolName, envelope, channelGrantSnapshot) {
1317
+ if (!index_js_1.MIRROR_READABLE_TOOLS.has(toolName) || !channelGrantSnapshot) {
1299
1318
  return undefined;
1300
1319
  }
1301
- return (0, read_js_1.mirrorReadScopes)(envelope, this.channelGrantProvider());
1320
+ return (0, read_js_1.mirrorReadScopes)(envelope, channelGrantSnapshot);
1302
1321
  }
1303
1322
  applyEnvelopeScopedReadDefaults(toolName, input, ctx) {
1304
1323
  // Memory read tools must inherit envelope scopes consistently when the
@@ -1340,7 +1359,7 @@ class GatewayToolExecutor {
1340
1359
  // READ defaulting: identity scopes plus the grant mirror, so an omitted
1341
1360
  // scopes arg recalls everything this run is ALLOWED to read instead of
1342
1361
  // silently less than the enforcer would accept.
1343
- const mirror = (0, read_js_1.mirrorReadScopes)(ctx.envelope, this.channelGrantProvider());
1362
+ const mirror = this.readScopeMirrorFor(toolName, ctx.envelope, ctx.channelGrantSnapshot) ?? [];
1344
1363
  const seen = new Set(ctx.envelope.scope.memory_scopes.map((scope) => `${scope.kind}:${scope.id}`));
1345
1364
  return {
1346
1365
  ...scopedInput,
@@ -1433,7 +1452,7 @@ class GatewayToolExecutor {
1433
1452
  typeof candidate.reasoning === 'string' &&
1434
1453
  candidate.reasoning.length > 0);
1435
1454
  }
1436
- logGatewayToolCall(ctx, toolName, result, durationMs, scopeAudit, gatewayCallId, error) {
1455
+ logGatewayToolCall(ctx, toolName, result, durationMs, scopeAudit, gatewayCallId, toolDetails, error) {
1437
1456
  try {
1438
1457
  if (!this.sessionsDb) {
1439
1458
  return;
@@ -1467,6 +1486,7 @@ class GatewayToolExecutor {
1467
1486
  ...(ctx?.workorderAttemptId !== undefined
1468
1487
  ? { workorder_attempt_id: ctx.workorderAttemptId }
1469
1488
  : {}),
1489
+ ...toolDetails,
1470
1490
  },
1471
1491
  });
1472
1492
  }
@@ -1474,6 +1494,16 @@ class GatewayToolExecutor {
1474
1494
  securityLogger.warn('[envelope] gateway audit log failed (non-fatal)', logErr);
1475
1495
  }
1476
1496
  }
1497
+ gatewayTraceDetails(toolName, result) {
1498
+ if (toolName !== 'report_publish')
1499
+ return {};
1500
+ const slotIds = result?.acceptedSlotIds;
1501
+ if (!Array.isArray(slotIds) || slotIds.some((slotId) => typeof slotId !== 'string'))
1502
+ return {};
1503
+ // Audit only the structural slot identity. Board HTML/content must never
1504
+ // enter gateway trace details.
1505
+ return { report_slot_ids: [...slotIds].sort() };
1506
+ }
1477
1507
  alarmScopeMismatch(ctx, toolName) {
1478
1508
  try {
1479
1509
  this.metricsStore?.record({
@@ -1643,11 +1673,73 @@ class GatewayToolExecutor {
1643
1673
  success: true,
1644
1674
  result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(await this.driveTools.download(input, this.getExecutionState().signal)),
1645
1675
  };
1646
- case 'drive_upload':
1647
- return {
1648
- success: true,
1649
- result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(await this.driveTools.upload(input)),
1650
- };
1676
+ case 'drive_upload': {
1677
+ const uploadInput = input;
1678
+ const state = this.getExecutionState();
1679
+ const sourceMessageRef = state.sourceMessageRef;
1680
+ if (!sourceMessageRef?.startsWith('owner-event:')) {
1681
+ return {
1682
+ success: true,
1683
+ result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(await this.driveTools.upload(uploadInput)),
1684
+ };
1685
+ }
1686
+ const effect = this.requireOwnerEventEffect(uploadInput.effect_key, 'drive_upload');
1687
+ const ledger = this.requireOwnerEventEffectLedger();
1688
+ const occurrence = `${sourceMessageRef}:drive:${effect.actionKey}`;
1689
+ let reservation = ledger.inspect(effect.batchId, effect.actionKey, 'drive_upload');
1690
+ if (reservation?.state === 'confirmed') {
1691
+ if (!reservation.result) {
1692
+ throw new Error('Confirmed Drive upload is missing its durable result');
1693
+ }
1694
+ return { success: true, result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(reservation.result) };
1695
+ }
1696
+ if (!reservation) {
1697
+ // Local validation and the read-only marker lookup are safely retryable.
1698
+ // Do them before the durable transition that means a create may have escaped.
1699
+ const preparation = await this.driveTools.prepareUpload(uploadInput, occurrence);
1700
+ reservation = ledger.begin(effect.batchId, effect.actionKey, 'drive_upload', {
1701
+ folderId: preparation.prepared.folderId,
1702
+ fileName: preparation.prepared.fileName,
1703
+ });
1704
+ if (reservation.state === 'confirmed') {
1705
+ if (!reservation.result) {
1706
+ throw new Error('Confirmed Drive upload is missing its durable result');
1707
+ }
1708
+ return { success: true, result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(reservation.result) };
1709
+ }
1710
+ if (reservation.state === 'execute' && preparation.existing) {
1711
+ ledger.confirm(effect.batchId, effect.actionKey, 'drive_upload', preparation.existing);
1712
+ return {
1713
+ success: true,
1714
+ result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(preparation.existing),
1715
+ };
1716
+ }
1717
+ if (reservation.state === 'execute') {
1718
+ try {
1719
+ const result = await this.driveTools.transmitPreparedUpload(preparation.prepared);
1720
+ ledger.confirm(effect.batchId, effect.actionKey, 'drive_upload', result);
1721
+ return { success: true, result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(result) };
1722
+ }
1723
+ catch (error) {
1724
+ ledger.markUnknown(effect.batchId, effect.actionKey, 'drive_upload', error instanceof Error ? error.message : String(error));
1725
+ throw error;
1726
+ }
1727
+ }
1728
+ }
1729
+ const folderId = reservation.intent.folderId;
1730
+ if (typeof folderId !== 'string') {
1731
+ throw new Error('Owner-event Drive reservation has invalid durable intent');
1732
+ }
1733
+ try {
1734
+ const result = await this.driveTools.recoverUpload(folderId, occurrence);
1735
+ ledger.confirm(effect.batchId, effect.actionKey, 'drive_upload', result);
1736
+ return { success: true, result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(result) };
1737
+ }
1738
+ catch (error) {
1739
+ ledger.markUnknown(effect.batchId, effect.actionKey, 'drive_upload', error instanceof Error ? error.message : String(error));
1740
+ throw error;
1741
+ }
1742
+ }
1651
1743
  // Browser tools
1652
1744
  case 'os_get_config':
1653
1745
  return await this.executeGetConfig(input);
@@ -1771,11 +1863,28 @@ class GatewayToolExecutor {
1771
1863
  }
1772
1864
  }
1773
1865
  if (this.reportPublisher) {
1774
- this.reportPublisher(slotsInput);
1775
- const slotNames = Object.keys(slotsInput);
1866
+ const publication = this.reportPublisher(slotsInput);
1867
+ // Backward compatibility: older injected publishers return void
1868
+ // or the exact changed slot array. Production distinguishes slots
1869
+ // accepted as present from slots whose HTML actually changed.
1870
+ const acceptedSlotIds = Array.isArray(publication)
1871
+ ? [...new Set(publication)].sort()
1872
+ : isReportPublishResult(publication)
1873
+ ? [...new Set(publication.acceptedSlotIds)].sort()
1874
+ : Object.keys(slotsInput).sort();
1875
+ const changedSlotIds = Array.isArray(publication)
1876
+ ? [...acceptedSlotIds]
1877
+ : isReportPublishResult(publication)
1878
+ ? [...new Set(publication.changedSlotIds)].sort()
1879
+ : [...acceptedSlotIds];
1880
+ if (acceptedSlotIds.length === 0) {
1881
+ throw new types_js_1.AgentError('Report publisher accepted no slots', 'TOOL_ERROR', undefined, false);
1882
+ }
1776
1883
  return {
1777
1884
  success: true,
1778
- message: `Dashboard updated: ${slotNames.join(', ')} (${slotNames.length} slots)`,
1885
+ acceptedSlotIds,
1886
+ changedSlotIds,
1887
+ message: `Dashboard report accepted: ${acceptedSlotIds.join(', ')} (${acceptedSlotIds.length} accepted, ${changedSlotIds.length} changed)`,
1779
1888
  };
1780
1889
  }
1781
1890
  throw new types_js_1.AgentError('Report publisher not configured', 'TOOL_ERROR', undefined, false);
@@ -2747,7 +2856,7 @@ class GatewayToolExecutor {
2747
2856
  * Execute telegram_send tool - Send message/file to Telegram chat
2748
2857
  */
2749
2858
  async executeTelegramSend(input) {
2750
- const { chat_id, message, file_path, sticker_emotion } = input;
2859
+ const { chat_id, message, file_path, sticker_emotion, delivery_key } = input;
2751
2860
  if (!chat_id) {
2752
2861
  return { success: false, error: 'chat_id is required' };
2753
2862
  }
@@ -2755,21 +2864,56 @@ class GatewayToolExecutor {
2755
2864
  return { success: false, error: 'Telegram gateway not configured' };
2756
2865
  }
2757
2866
  const sourceMessageRef = this.getExecutionState().sourceMessageRef;
2867
+ const ownerEvent = sourceMessageRef?.startsWith('owner-event:') === true;
2868
+ const deliveryVariant = sticker_emotion
2869
+ ? 'sticker'
2870
+ : file_path
2871
+ ? TELEGRAM_PHOTO_EXTENSIONS.has((0, path_1.extname)(file_path).toLowerCase())
2872
+ ? 'image'
2873
+ : 'file'
2874
+ : 'text';
2875
+ let ownerEffect = null;
2876
+ let ownerLedger = null;
2877
+ try {
2878
+ if (ownerEvent) {
2879
+ ownerEffect = this.requireOwnerEventEffect(delivery_key, 'telegram_send');
2880
+ ownerLedger = this.requireOwnerEventEffectLedger();
2881
+ const reservation = ownerLedger.begin(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send', { chatId: chat_id, variant: deliveryVariant });
2882
+ if (reservation.state === 'confirmed')
2883
+ return { success: true };
2884
+ if (reservation.intent.chatId !== chat_id) {
2885
+ return {
2886
+ success: false,
2887
+ error: 'owner-event Telegram target differs from the host-reserved destination',
2888
+ };
2889
+ }
2890
+ if (reservation.intent.variant !== deliveryVariant) {
2891
+ return {
2892
+ success: false,
2893
+ error: 'owner-event Telegram retry must use the originally reserved delivery variant',
2894
+ };
2895
+ }
2896
+ }
2897
+ }
2898
+ catch (error) {
2899
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
2900
+ }
2758
2901
  const idempotencyKey = sourceMessageRef
2759
- ? (0, crypto_1.createHash)('sha256')
2760
- .update(JSON.stringify([
2761
- sourceMessageRef,
2762
- chat_id,
2763
- message ?? null,
2764
- file_path ?? null,
2765
- sticker_emotion ?? null,
2766
- ]))
2767
- .digest('hex')
2902
+ ? ownerEvent
2903
+ ? `${sourceMessageRef}:telegram:${delivery_key}`
2904
+ : (0, crypto_1.createHash)('sha256')
2905
+ .update(JSON.stringify([
2906
+ sourceMessageRef,
2907
+ chat_id,
2908
+ message ?? null,
2909
+ file_path ?? null,
2910
+ sticker_emotion ?? null,
2911
+ ]))
2912
+ .digest('hex')
2768
2913
  : undefined;
2769
2914
  try {
2770
2915
  if (sticker_emotion) {
2771
- await (this.telegramGateway.sendStickerFromActiveTurn?.(chat_id, sticker_emotion) ??
2772
- this.telegramGateway.sendSticker(chat_id, sticker_emotion));
2916
+ await (this.telegramGateway.sendStickerFromActiveTurn?.(chat_id, sticker_emotion, idempotencyKey) ?? this.telegramGateway.sendSticker(chat_id, sticker_emotion, idempotencyKey));
2773
2917
  }
2774
2918
  else if (file_path) {
2775
2919
  const safePath = resolvePrivateWorkspaceFile(file_path);
@@ -2820,12 +2964,36 @@ class GatewayToolExecutor {
2820
2964
  error: 'Either message, file_path, or sticker_emotion is required',
2821
2965
  };
2822
2966
  }
2967
+ if (ownerEffect && ownerLedger) {
2968
+ ownerLedger.confirm(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send', null);
2969
+ }
2823
2970
  return { success: true };
2824
2971
  }
2825
2972
  catch (err) {
2973
+ if (ownerEffect && ownerLedger) {
2974
+ ownerLedger.markUnknown(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send', err instanceof Error ? err.message : String(err));
2975
+ }
2826
2976
  return { success: false, error: `Failed to send to Telegram: ${err}` };
2827
2977
  }
2828
2978
  }
2979
+ requireOwnerEventEffect(actionKey, toolName) {
2980
+ const state = this.getExecutionState();
2981
+ const authority = state.ownerEventEffects;
2982
+ if (!authority || state.sourceMessageRef !== `owner-event:${authority.batchId}`) {
2983
+ throw new Error(`owner-event ${toolName} requires host-issued effect authority`);
2984
+ }
2985
+ const expectedKey = authority.effectKeys[toolName];
2986
+ if (actionKey !== expectedKey) {
2987
+ throw new Error(`owner-event ${toolName} requires the host-issued ${toolName} key ${expectedKey}`);
2988
+ }
2989
+ return { batchId: authority.batchId, actionKey };
2990
+ }
2991
+ requireOwnerEventEffectLedger() {
2992
+ if (!this.ownerEventEffectLedger) {
2993
+ throw new Error('owner-event external effects are disabled without the durable effect ledger');
2994
+ }
2995
+ return this.ownerEventEffectLedger;
2996
+ }
2829
2997
  /**
2830
2998
  * Execute os_get_config tool - Get current configuration
2831
2999
  * Masks sensitive data for non-viewer sources
@@ -3279,7 +3447,7 @@ class GatewayToolExecutor {
3279
3447
  // back excerpts from every other channel of the connector. Citation would then be
3280
3448
  // strictly wider than reading, which is the one thing this path must never be.
3281
3449
  const citationChannels = ctx.envelope
3282
- ? (0, read_js_1.narrowGrantToEnvelope)((0, read_js_1.liveBoundaryChannels)(), {
3450
+ ? (0, read_js_1.narrowGrantToEnvelope)(ctx.channelGrantSnapshot ?? {}, {
3283
3451
  connectors,
3284
3452
  scopes: ctx.envelope.scope.memory_scopes ?? [],
3285
3453
  })
@@ -3424,6 +3592,7 @@ class GatewayToolExecutor {
3424
3592
  caller: 'gateway',
3425
3593
  envelope: ctx.envelope,
3426
3594
  modelRunId: ctx.modelRunId ?? null,
3595
+ channelGrantSnapshot: ctx.channelGrantSnapshot,
3427
3596
  input: effectiveInput,
3428
3597
  signal: ctx.signal,
3429
3598
  beforePersist: temporalContext
@@ -3545,6 +3714,10 @@ function getContextPacketIdForTrustedProvenance(input) {
3545
3714
  function dedupeSourceRefs(refs) {
3546
3715
  return [...new Set(refs)];
3547
3716
  }
3717
+ function snapshotChannelGrant(provider) {
3718
+ const liveGrant = provider();
3719
+ return Object.fromEntries(Object.entries(liveGrant).map(([connector, channels]) => [connector, [...channels]]));
3720
+ }
3548
3721
  function normalizeMemoryScopes(value) {
3549
3722
  if (!Array.isArray(value) || value.length === 0) {
3550
3723
  return null;