@opengeni/core 0.21.10 → 0.24.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 (116) hide show
  1. package/dist/access/index.d.ts +1 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +2 -2
  4. package/dist/billing/limits.d.ts +1 -1
  5. package/dist/chunk-6WKPWE5S.js +2380 -0
  6. package/dist/chunk-6WKPWE5S.js.map +1 -0
  7. package/dist/chunk-JJU6I5XD.js +2713 -0
  8. package/dist/chunk-JJU6I5XD.js.map +1 -0
  9. package/dist/chunk-NYPMQ7NO.js +2270 -0
  10. package/dist/chunk-NYPMQ7NO.js.map +1 -0
  11. package/dist/dependencies.d.ts +26 -6
  12. package/dist/domain/capabilities.d.ts +28 -3
  13. package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
  14. package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
  15. package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
  16. package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
  17. package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
  18. package/dist/domain/editable-artifacts/errors.d.ts +47 -0
  19. package/dist/domain/editable-artifacts/hash.d.ts +11 -0
  20. package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
  21. package/dist/domain/editable-artifacts/index.d.ts +14 -0
  22. package/dist/domain/editable-artifacts/ports.d.ts +341 -0
  23. package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
  24. package/dist/domain/editable-artifacts/service.d.ts +105 -0
  25. package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
  26. package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
  27. package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
  28. package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
  29. package/dist/domain/editable-artifacts/types.d.ts +410 -0
  30. package/dist/domain/memory-slack-delivery.d.ts +19 -0
  31. package/dist/domain/packs.d.ts +29 -1
  32. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  33. package/dist/domain/scheduled-tasks.d.ts +2 -2
  34. package/dist/domain/session-tool-policy.d.ts +2 -2
  35. package/dist/domain/sessions.d.ts +14 -6
  36. package/dist/domain/skill-imports.d.ts +32 -0
  37. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  38. package/dist/domain/timeline-annotations.d.ts +5 -0
  39. package/dist/domain/video-generation-capabilities.d.ts +9 -0
  40. package/dist/domain/video-generation.d.ts +47 -0
  41. package/dist/editable-artifact-live/application.d.ts +69 -0
  42. package/dist/editable-artifact-live/errors.d.ts +1 -0
  43. package/dist/editable-artifact-live/index.d.ts +8 -0
  44. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  45. package/dist/editable-artifact-live/ports.d.ts +100 -0
  46. package/dist/editable-artifact-live/server.d.ts +71 -0
  47. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  48. package/dist/editable-artifact-live/types.d.ts +213 -0
  49. package/dist/editable-artifact-live/wire.d.ts +34 -0
  50. package/dist/editable-artifact-live.d.ts +2 -0
  51. package/dist/editable-artifact-live.js +58 -0
  52. package/dist/editable-artifact-live.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +2 -0
  54. package/dist/editable-artifacts.js +179 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/index.d.ts +35 -26
  57. package/dist/index.js +2792 -609
  58. package/dist/index.js.map +1 -1
  59. package/dist/managed-session.d.ts +1 -1
  60. package/dist/rigs/index.d.ts +1 -1
  61. package/dist/rigs/provider-images.d.ts +31 -0
  62. package/dist/sandbox/routing.d.ts +1 -0
  63. package/dist/session-authorization.d.ts +1 -1
  64. package/dist/workflow-wake-contract.d.ts +7 -0
  65. package/package.json +18 -10
  66. package/src/application/new-session-drafts.ts +2 -0
  67. package/src/application/session-commands.ts +188 -107
  68. package/src/dependencies.ts +30 -2
  69. package/src/domain/capabilities.ts +604 -220
  70. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  71. package/src/domain/durable-learning-slack-publication.ts +202 -0
  72. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  73. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  74. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  75. package/src/domain/editable-artifacts/errors.ts +115 -0
  76. package/src/domain/editable-artifacts/hash.ts +131 -0
  77. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  78. package/src/domain/editable-artifacts/index.ts +14 -0
  79. package/src/domain/editable-artifacts/ports.ts +462 -0
  80. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  81. package/src/domain/editable-artifacts/service.ts +2402 -0
  82. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  83. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  84. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  85. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  86. package/src/domain/editable-artifacts/types.ts +835 -0
  87. package/src/domain/insights.ts +136 -33
  88. package/src/domain/memory-slack-delivery.ts +204 -0
  89. package/src/domain/memory-slack-publication.ts +5 -2
  90. package/src/domain/packs.ts +266 -5
  91. package/src/domain/personal-connection-delegations.ts +97 -6
  92. package/src/domain/scheduled-tasks.ts +155 -29
  93. package/src/domain/session-tool-policy.ts +8 -2
  94. package/src/domain/sessions.ts +274 -131
  95. package/src/domain/skill-imports.ts +350 -0
  96. package/src/domain/slack-publication-secret-safety.ts +42 -0
  97. package/src/domain/timeline-annotations.ts +204 -0
  98. package/src/domain/video-generation-capabilities.ts +69 -0
  99. package/src/domain/video-generation.ts +249 -0
  100. package/src/editable-artifact-live/application.ts +182 -0
  101. package/src/editable-artifact-live/errors.ts +4 -0
  102. package/src/editable-artifact-live/index.ts +8 -0
  103. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  104. package/src/editable-artifact-live/ports.ts +139 -0
  105. package/src/editable-artifact-live/server.ts +1707 -0
  106. package/src/editable-artifact-live/ticket.ts +217 -0
  107. package/src/editable-artifact-live/types.ts +286 -0
  108. package/src/editable-artifact-live/wire.ts +95 -0
  109. package/src/editable-artifact-live.ts +2 -0
  110. package/src/editable-artifacts.ts +2 -0
  111. package/src/index.ts +13 -0
  112. package/src/rigs/index.ts +26 -19
  113. package/src/rigs/provider-images.ts +110 -0
  114. package/src/sandbox/routing.ts +6 -3
  115. package/src/session-authorization.ts +3 -0
  116. package/src/workflow-wake-contract.ts +8 -0
@@ -18,6 +18,7 @@ import {
18
18
  listFloorSessions,
19
19
  listLiveWarmLeases,
20
20
  listModelCallFacets,
21
+ listRecentModelCalls,
21
22
  listScheduledTasks,
22
23
  requireWorkspace,
23
24
  sumUsageQuantity,
@@ -37,7 +38,7 @@ export type GetWorkspaceInsightsInput = {
37
38
  };
38
39
 
39
40
  function microsToUsd(micros: number): number {
40
- return Math.round((micros / 1_000_000) * 100) / 100;
41
+ return micros / 1_000_000;
41
42
  }
42
43
 
43
44
  function cacheHitPct(cached: number, input: number): number {
@@ -163,6 +164,12 @@ function billingPathOf(value: string): "opengeni_credits" | "external" {
163
164
  return value === "external" ? "external" : "opengeni_credits";
164
165
  }
165
166
 
167
+ function pricingSourceOf(
168
+ value: string | null,
169
+ ): "configured_list_price" | "gateway_reported" | null {
170
+ return value === "configured_list_price" || value === "gateway_reported" ? value : null;
171
+ }
172
+
166
173
  export async function getWorkspaceInsights(
167
174
  db: Database,
168
175
  settings: Settings,
@@ -197,6 +204,7 @@ export async function getWorkspaceInsights(
197
204
  billableTokensUsed,
198
205
  agentRunsUsed,
199
206
  facets,
207
+ recentCalls,
200
208
  ] = await Promise.all([
201
209
  sumUsageQuantityInRange(db, {
202
210
  workspaceId: input.workspaceId,
@@ -276,7 +284,7 @@ export async function getWorkspaceInsights(
276
284
  aggregateSessionDepth(db, input.workspaceId),
277
285
  listFloorSessions(db, input.workspaceId, 24),
278
286
  settings.sandboxSelfhostedEnabled
279
- ? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS)
287
+ ? countOnlineMachines(db, input.workspaceId, MACHINE_HEARTBEAT_FRESH_MS, now)
280
288
  : Promise.resolve(0),
281
289
  sumUsageQuantity(db, {
282
290
  workspaceId: input.workspaceId,
@@ -293,23 +301,37 @@ export async function getWorkspaceInsights(
293
301
  since: window.since,
294
302
  until: window.until,
295
303
  }),
304
+ listRecentModelCalls(db, {
305
+ workspaceId: input.workspaceId,
306
+ since: window.since,
307
+ until: window.until,
308
+ ...filter,
309
+ limit: 50,
310
+ }),
296
311
  ]);
297
312
 
298
- // Exact prior costs for the current top drivers — never a separate top-N page that
299
- // drops roots and invents +$full as "new" spend.
300
- const priorRootDrivers = await aggregateRootSessionDrivers(db, {
301
- workspaceId: input.workspaceId,
302
- since: window.priorSince,
303
- until: window.priorUntil,
304
- ...filter,
305
- rootSessionIds: rootDrivers.map((row) => row.rootSessionId),
306
- });
307
-
308
- const attached = await countSessionsAttachedToGroups(
309
- db,
310
- input.workspaceId,
311
- warmGroups.map((group) => group.groupId),
312
- );
313
+ const [priorRootDrivers, attached, fireCounts] = await Promise.all([
314
+ // Exact prior costs for the current top drivers — never a separate top-N page that
315
+ // drops roots and invents +$full as "new" spend.
316
+ aggregateRootSessionDrivers(db, {
317
+ workspaceId: input.workspaceId,
318
+ since: window.priorSince,
319
+ until: window.priorUntil,
320
+ ...filter,
321
+ rootSessionIds: rootDrivers.map((row) => row.rootSessionId),
322
+ }),
323
+ countSessionsAttachedToGroups(
324
+ db,
325
+ input.workspaceId,
326
+ warmGroups.map((group) => group.groupId),
327
+ ),
328
+ countScheduledTaskFires(db, {
329
+ workspaceId: input.workspaceId,
330
+ since: window.since,
331
+ until: window.until,
332
+ taskIds: tasks.map((task) => task.id),
333
+ }),
334
+ ]);
313
335
  const backendByGroup = new Map(liveWarm.map((lease) => [lease.groupId, lease.backend]));
314
336
  const warmSecondsByGroup = new Map(warmGroups.map((group) => [group.groupId, group.warmSeconds]));
315
337
 
@@ -323,24 +345,58 @@ export async function getWorkspaceInsights(
323
345
  inputTokens: row.inputTokens,
324
346
  outputTokens: row.outputTokens,
325
347
  cachedTokens: row.cachedTokens,
348
+ cacheInputTokens: row.cacheInputTokens,
326
349
  cacheWriteTokens: row.cacheWriteTokens,
327
350
  reasoningTokens: row.reasoningTokens,
351
+ totalTokens: row.totalTokens,
352
+ tokenKnownCalls: row.tokenKnownCalls,
353
+ cacheKnownCalls: row.cacheKnownCalls,
328
354
  creditUsd: microsToUsd(row.pricedCostMicros),
355
+ estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
356
+ estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
329
357
  }))
330
- .sort((a, b) => b.inputTokens - a.inputTokens);
358
+ .sort((a, b) => b.totalTokens - a.totalTokens);
331
359
 
332
360
  const creditMicros = modelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
333
361
  const priorCreditMicros = priorModelRows.reduce((sum, row) => sum + row.pricedCostMicros, 0);
362
+ const estimatedProviderCostMicros = modelRows.reduce(
363
+ (sum, row) => sum + row.estimatedProviderCostMicros,
364
+ 0,
365
+ );
366
+ const priorEstimatedProviderCostMicros = priorModelRows.reduce(
367
+ (sum, row) => sum + row.estimatedProviderCostMicros,
368
+ 0,
369
+ );
370
+ const estimatedProviderCostKnownCalls = modelRows.reduce(
371
+ (sum, row) => sum + row.estimatedProviderCostKnownCalls,
372
+ 0,
373
+ );
374
+ const priorEstimatedProviderCostKnownCalls = priorModelRows.reduce(
375
+ (sum, row) => sum + row.estimatedProviderCostKnownCalls,
376
+ 0,
377
+ );
378
+ const modelCalls = modelRows.reduce((sum, row) => sum + row.calls, 0);
334
379
  const priorInputTokens = priorModelRows.reduce((sum, row) => sum + row.inputTokens, 0);
380
+ const priorTotalTokens = priorModelRows.reduce((sum, row) => sum + row.totalTokens, 0);
335
381
  const priorCachedTokens = priorModelRows.reduce((sum, row) => sum + row.cachedTokens, 0);
382
+ const priorCacheInputTokens = priorModelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
336
383
  const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
337
384
 
338
385
  const days = enumerateUtcDays(window.since, window.until);
339
386
  const series = days.map((day) => {
340
387
  const facts = factDays.get(day) ?? {
341
388
  costMicros: 0,
389
+ estimatedProviderCostMicros: 0,
390
+ estimatedProviderCostKnownCalls: 0,
342
391
  inputTokens: 0,
392
+ outputTokens: 0,
343
393
  cachedTokens: 0,
394
+ cacheInputTokens: 0,
395
+ cacheWriteTokens: 0,
396
+ reasoningTokens: 0,
397
+ totalTokens: 0,
398
+ tokenKnownCalls: 0,
399
+ cacheKnownCalls: 0,
344
400
  calls: 0,
345
401
  };
346
402
  const modelCostMicros = modelFilterActive
@@ -349,10 +405,19 @@ export async function getWorkspaceInsights(
349
405
  return {
350
406
  label: day.slice(5),
351
407
  modelCostUsd: microsToUsd(modelCostMicros),
408
+ estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
409
+ estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
352
410
  warmSeconds: warmDays.get(day) ?? 0,
353
411
  inputTokens: facts.inputTokens,
412
+ outputTokens: facts.outputTokens,
354
413
  cachedTokens: facts.cachedTokens,
355
- cacheHitPct: cacheHitPct(facts.cachedTokens, facts.inputTokens),
414
+ cacheInputTokens: facts.cacheInputTokens,
415
+ cacheWriteTokens: facts.cacheWriteTokens,
416
+ reasoningTokens: facts.reasoningTokens,
417
+ totalTokens: facts.totalTokens,
418
+ tokenKnownCalls: facts.tokenKnownCalls,
419
+ cacheKnownCalls: facts.cacheKnownCalls,
420
+ cacheHitPct: cacheHitPct(facts.cachedTokens, facts.cacheInputTokens),
356
421
  calls: facts.calls,
357
422
  };
358
423
  });
@@ -360,7 +425,10 @@ export async function getWorkspaceInsights(
360
425
  const priorDriverByRoot = new Map(
361
426
  priorRootDrivers.map((row) => [row.rootSessionId, row.pricedCostMicros]),
362
427
  );
363
- const creditUsdForPct = Math.max(microsToUsd(creditMicros), 0.01);
428
+ const totalTokensForPct = Math.max(
429
+ rootDrivers.reduce((sum, row) => sum + row.totalTokens, 0),
430
+ 1,
431
+ );
364
432
  const drivers = rootDrivers.map((row) => {
365
433
  const creditUsd = microsToUsd(row.pricedCostMicros);
366
434
  const priorUsd = microsToUsd(priorDriverByRoot.get(row.rootSessionId) ?? 0);
@@ -369,19 +437,19 @@ export async function getWorkspaceInsights(
369
437
  groupBy: "root_session" as const,
370
438
  label: row.title?.trim() || row.rootSessionId.slice(0, 8),
371
439
  creditUsd,
372
- tokens: row.inputTokens,
373
- cacheHitPct: cacheHitPct(row.cachedTokens, row.inputTokens),
374
- pctOfCreditUsd: Math.min(100, Math.round((creditUsd / creditUsdForPct) * 100)),
375
- deltaUsdVsPrior: Math.round((creditUsd - priorUsd) * 100) / 100,
440
+ estimatedProviderUsd: microsToUsd(row.estimatedProviderCostMicros),
441
+ estimatedProviderCostKnownCalls: row.estimatedProviderCostKnownCalls,
442
+ tokens: row.totalTokens,
443
+ cacheHitPct: cacheHitPct(row.cachedTokens, row.cacheInputTokens),
444
+ pctOfCreditUsd:
445
+ creditMicros > 0
446
+ ? Math.min(100, Math.round((row.pricedCostMicros / creditMicros) * 100))
447
+ : 0,
448
+ pctOfTokens: Math.min(100, Math.round((row.totalTokens / totalTokensForPct) * 100)),
449
+ deltaUsdVsPrior: creditUsd - priorUsd,
376
450
  };
377
451
  });
378
452
 
379
- const fireCounts = await countScheduledTaskFires(db, {
380
- workspaceId: input.workspaceId,
381
- since: window.since,
382
- until: window.until,
383
- taskIds: tasks.map((task) => task.id),
384
- });
385
453
  const scheduleFactById = new Map(scheduleFacts.map((row) => [row.scheduledTaskId, row] as const));
386
454
  const schedules = tasks.map((task) => {
387
455
  const fact = scheduleFactById.get(task.id);
@@ -390,8 +458,10 @@ export async function getWorkspaceInsights(
390
458
  name: task.name,
391
459
  fires: fireCounts.get(task.id) ?? 0,
392
460
  creditUsd: fact ? microsToUsd(fact.pricedCostMicros) : null,
393
- tokens: fact ? fact.inputTokens : null,
394
- cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.inputTokens) : null,
461
+ estimatedProviderUsd: fact ? microsToUsd(fact.estimatedProviderCostMicros) : null,
462
+ estimatedProviderCostKnownCalls: fact ? fact.estimatedProviderCostKnownCalls : null,
463
+ tokens: fact ? fact.totalTokens : null,
464
+ cacheHitPct: fact ? cacheHitPct(fact.cachedTokens, fact.cacheInputTokens) : null,
395
465
  billing: fact ? billingPathOf(fact.billingPath) : null,
396
466
  };
397
467
  });
@@ -422,6 +492,9 @@ export async function getWorkspaceInsights(
422
492
  priorLabel: window.priorLabel,
423
493
  seriesLabel: window.seriesLabel,
424
494
  cacheSeriesLabel: window.cacheSeriesLabel,
495
+ windowStart: window.since.toISOString(),
496
+ windowEnd: window.until.toISOString(),
497
+ generatedAt: now.toISOString(),
425
498
  timezone: "UTC",
426
499
  models,
427
500
  facets,
@@ -432,6 +505,30 @@ export async function getWorkspaceInsights(
432
505
  })),
433
506
  drivers,
434
507
  schedules,
508
+ recentCalls: recentCalls.map((row) => ({
509
+ id: row.id,
510
+ occurredAt: row.occurredAt.toISOString(),
511
+ recordedAt: row.recordedAt.toISOString(),
512
+ sessionId: row.sessionId,
513
+ sessionTitle: row.sessionTitle?.trim() || "Untitled session",
514
+ turnId: row.turnId,
515
+ provider: row.provider,
516
+ providerApi: row.providerApi,
517
+ model: row.model,
518
+ billing: billingPathOf(row.billingPath),
519
+ inputTokens: row.inputTokens,
520
+ outputTokens: row.outputTokens,
521
+ cachedTokens: row.cachedTokens,
522
+ cacheWriteTokens: row.cacheWriteTokens,
523
+ reasoningTokens: row.reasoningTokens,
524
+ totalTokens: row.totalTokens,
525
+ creditUsd: microsToUsd(row.pricedCostMicros),
526
+ estimatedProviderUsd:
527
+ row.estimatedProviderCostMicros == null
528
+ ? null
529
+ : microsToUsd(row.estimatedProviderCostMicros),
530
+ pricingSource: pricingSourceOf(row.pricingSource),
531
+ })),
435
532
  warmSeconds,
436
533
  priorWarmSeconds,
437
534
  warmGroups: warmGroups.map((group) => ({
@@ -458,8 +555,14 @@ export async function getWorkspaceInsights(
458
555
  priorWorkspaceCreditUsd: microsToUsd(priorWorkspaceCreditMicros),
459
556
  creditUsd: microsToUsd(creditMicros),
460
557
  priorCreditUsd: microsToUsd(priorCreditMicros),
558
+ estimatedProviderUsd: microsToUsd(estimatedProviderCostMicros),
559
+ priorEstimatedProviderUsd: microsToUsd(priorEstimatedProviderCostMicros),
560
+ estimatedProviderCostKnownCalls,
561
+ priorEstimatedProviderCostKnownCalls,
562
+ modelCalls,
461
563
  priorInputTokens,
462
- priorCacheHitPct: cacheHitPct(priorCachedTokens, priorInputTokens),
564
+ priorTotalTokens,
565
+ priorCacheHitPct: cacheHitPct(priorCachedTokens, priorCacheInputTokens),
463
566
  priorCalls,
464
567
  goalsActive: depth.goalsActive,
465
568
  goalsCompleted: depth.goalsCompleted,
@@ -0,0 +1,204 @@
1
+ import type { MemorySlackPublicationDistribution } from "@opengeni/contracts";
2
+ import {
3
+ correctWorkspaceMemory,
4
+ enqueueMemorySlackPublication,
5
+ getCurrentMemorySlackPublicationConfiguration,
6
+ getWorkspaceMemorySlackPublicationSnapshot,
7
+ saveWorkspaceMemory,
8
+ withWorkspaceRls,
9
+ type CorrectWorkspaceMemoryInput,
10
+ type CorrectWorkspaceMemoryResult,
11
+ type Database,
12
+ type EnqueueMemorySlackPublicationResult,
13
+ type MemoryEmbedder,
14
+ type MemorySlackPublicationActor,
15
+ type SaveWorkspaceMemoryInput,
16
+ type SaveWorkspaceMemoryResult,
17
+ type WorkspaceMemorySlackPublicationSnapshot,
18
+ } from "@opengeni/db";
19
+ import {
20
+ evaluateMemorySlackPublication,
21
+ type MemorySlackOrigin,
22
+ type MemorySlackPublicationDecision,
23
+ } from "./memory-slack-publication";
24
+
25
+ export type MemorySlackPublicationCommitRequest = {
26
+ distribution: MemorySlackPublicationDistribution;
27
+ actor: MemorySlackPublicationActor;
28
+ ownerLabel?: string | null;
29
+ origin?: MemorySlackOrigin;
30
+ };
31
+
32
+ export type MemorySlackPublicationCommitResult = {
33
+ decision: MemorySlackPublicationDecision | null;
34
+ enqueue: EnqueueMemorySlackPublicationResult | null;
35
+ };
36
+
37
+ export async function saveWorkspaceMemoryWithSlackPublication(
38
+ db: Database,
39
+ input: SaveWorkspaceMemoryInput,
40
+ publication: MemorySlackPublicationCommitRequest | null,
41
+ embedder?: MemoryEmbedder,
42
+ ): Promise<SaveWorkspaceMemoryResult & { slackPublication: MemorySlackPublicationCommitResult }> {
43
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
44
+ const result = await saveWorkspaceMemory(scopedDb, input, embedder);
45
+ const slackPublication = publication
46
+ ? await publishSavedMemoryMutation(scopedDb, input, result, publication)
47
+ : { decision: null, enqueue: null };
48
+ return { ...result, slackPublication };
49
+ });
50
+ }
51
+
52
+ export async function correctWorkspaceMemoryWithSlackPublication(
53
+ db: Database,
54
+ input: CorrectWorkspaceMemoryInput,
55
+ publication: MemorySlackPublicationCommitRequest | null,
56
+ embedder?: MemoryEmbedder,
57
+ ): Promise<
58
+ CorrectWorkspaceMemoryResult & { slackPublication: MemorySlackPublicationCommitResult }
59
+ > {
60
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
61
+ const result = await correctWorkspaceMemory(scopedDb, input, embedder);
62
+ if (!publication || result.action !== "superseded" || !result.replacement) {
63
+ return { ...result, slackPublication: { decision: null, enqueue: null } };
64
+ }
65
+ const replacement = await requiredSnapshot(scopedDb, input.workspaceId, result.replacement.id);
66
+ const slackPublication = await evaluateAndEnqueue(scopedDb, {
67
+ accountId: input.accountId,
68
+ workspaceId: input.workspaceId,
69
+ snapshot: replacement,
70
+ changeKind: "corrected",
71
+ relatedMemoryId: result.memory.id,
72
+ occurredAt: result.replacement.updatedAt,
73
+ publication,
74
+ });
75
+ return { ...result, slackPublication };
76
+ });
77
+ }
78
+
79
+ async function publishSavedMemoryMutation(
80
+ db: Database,
81
+ input: SaveWorkspaceMemoryInput,
82
+ result: SaveWorkspaceMemoryResult,
83
+ publication: MemorySlackPublicationCommitRequest,
84
+ ): Promise<MemorySlackPublicationCommitResult> {
85
+ if (result.updated || (result.deduped && !result.superseded)) {
86
+ return { decision: null, enqueue: null };
87
+ }
88
+
89
+ if (result.superseded) {
90
+ const replacement = await requiredSnapshot(db, input.workspaceId, result.memory.id);
91
+ if (replacement.supersedesId === result.superseded.id) {
92
+ return await evaluateAndEnqueue(db, {
93
+ accountId: input.accountId,
94
+ workspaceId: input.workspaceId,
95
+ snapshot: replacement,
96
+ changeKind: "corrected",
97
+ relatedMemoryId: result.superseded.id,
98
+ occurredAt: result.memory.updatedAt,
99
+ publication,
100
+ });
101
+ }
102
+ const retired = await requiredSnapshot(db, input.workspaceId, result.superseded.id);
103
+ return await evaluateAndEnqueue(db, {
104
+ accountId: input.accountId,
105
+ workspaceId: input.workspaceId,
106
+ snapshot: retired,
107
+ changeKind: "superseded",
108
+ relatedMemoryId: result.memory.id,
109
+ occurredAt: result.superseded.updatedAt,
110
+ publication,
111
+ });
112
+ }
113
+
114
+ const created = await requiredSnapshot(db, input.workspaceId, result.memory.id);
115
+ return await evaluateAndEnqueue(db, {
116
+ accountId: input.accountId,
117
+ workspaceId: input.workspaceId,
118
+ snapshot: created,
119
+ changeKind: "created",
120
+ relatedMemoryId: null,
121
+ occurredAt: result.memory.createdAt,
122
+ publication,
123
+ });
124
+ }
125
+
126
+ async function evaluateAndEnqueue(
127
+ db: Database,
128
+ input: {
129
+ accountId: string;
130
+ workspaceId: string;
131
+ snapshot: WorkspaceMemorySlackPublicationSnapshot;
132
+ changeKind: "created" | "corrected" | "superseded";
133
+ relatedMemoryId: string | null;
134
+ occurredAt: string;
135
+ publication: MemorySlackPublicationCommitRequest;
136
+ },
137
+ ): Promise<MemorySlackPublicationCommitResult> {
138
+ const configuration = await getCurrentMemorySlackPublicationConfiguration(db, input.workspaceId);
139
+ const decision = evaluateMemorySlackPublication({
140
+ context: {
141
+ accountId: input.accountId,
142
+ workspaceId: input.workspaceId,
143
+ now: input.occurredAt,
144
+ },
145
+ policy: configuration
146
+ ? {
147
+ enabled: configuration.enabled,
148
+ autoImportances: configuration.autoImportances,
149
+ reviewImportances: configuration.reviewImportances,
150
+ }
151
+ : undefined,
152
+ memory: {
153
+ sourceType: "workspace_memory",
154
+ accountId: input.snapshot.accountId,
155
+ workspaceId: input.snapshot.workspaceId,
156
+ id: input.snapshot.id,
157
+ version: input.snapshot.version,
158
+ status: input.snapshot.status as Parameters<
159
+ typeof evaluateMemorySlackPublication
160
+ >[0]["memory"]["status"],
161
+ kind: input.snapshot.kind as Parameters<
162
+ typeof evaluateMemorySlackPublication
163
+ >[0]["memory"]["kind"],
164
+ scopeType: input.snapshot.scopeType as Parameters<
165
+ typeof evaluateMemorySlackPublication
166
+ >[0]["memory"]["scopeType"],
167
+ namespace: input.snapshot.namespace,
168
+ labels: input.snapshot.labels,
169
+ validFrom: input.snapshot.validFrom,
170
+ validUntil: input.snapshot.validUntil,
171
+ supersedesId: input.snapshot.supersedesId,
172
+ supersededById: input.snapshot.supersededById,
173
+ },
174
+ change: {
175
+ kind: input.changeKind,
176
+ relatedMemoryId: input.relatedMemoryId,
177
+ occurredAt: input.occurredAt,
178
+ origin: input.publication.origin ?? "native",
179
+ ownerLabel: input.publication.ownerLabel ?? null,
180
+ },
181
+ distribution: input.publication.distribution,
182
+ });
183
+ if (!decision.eligible) return { decision, enqueue: null };
184
+
185
+ const enqueue = await enqueueMemorySlackPublication(db, {
186
+ accountId: input.accountId,
187
+ workspaceId: input.workspaceId,
188
+ sourceType: "workspace_memory",
189
+ sourceId: input.snapshot.id,
190
+ sourceVersion: String(input.snapshot.version),
191
+ sourceIdempotencyKey: decision.idempotencyKey,
192
+ projection: decision.projection,
193
+ importance: decision.projection.importance,
194
+ deliveryMode: decision.deliveryMode,
195
+ actor: input.publication.actor,
196
+ });
197
+ return { decision, enqueue };
198
+ }
199
+
200
+ async function requiredSnapshot(db: Database, workspaceId: string, memoryId: string) {
201
+ const snapshot = await getWorkspaceMemorySlackPublicationSnapshot(db, workspaceId, memoryId);
202
+ if (!snapshot) throw new Error("Memory Slack publication source disappeared before commit");
203
+ return snapshot;
204
+ }
@@ -4,6 +4,7 @@ import {
4
4
  type KnowledgeMemoryKind,
5
5
  type KnowledgeMemoryStatus,
6
6
  } from "@opengeni/contracts";
7
+ import { sanitizeSlackPublicationText } from "./slack-publication-secret-safety";
7
8
 
8
9
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
9
10
  const SELECTOR_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
@@ -197,7 +198,9 @@ export function evaluateMemorySlackPublication(
197
198
  const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
198
199
  if (!deliveryMode) return denied("below_noise_policy");
199
200
 
200
- const collapsedSummary = collapseText(input.distribution.shareSummary);
201
+ const collapsedSummary = sanitizeSlackPublicationText(
202
+ collapseText(input.distribution.shareSummary),
203
+ );
201
204
  if (!collapsedSummary) return denied("missing_summary");
202
205
  const summary = truncateUtf8(collapsedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
203
206
 
@@ -397,7 +400,7 @@ function boundedOptionalText(
397
400
  value: string | null | undefined,
398
401
  maxBytes: number,
399
402
  ): { value: string | null; truncated: boolean } {
400
- const collapsed = collapseText(value ?? "");
403
+ const collapsed = sanitizeSlackPublicationText(collapseText(value ?? ""));
401
404
  if (!collapsed) return { value: null, truncated: false };
402
405
  const bounded = truncateUtf8(collapsed, maxBytes);
403
406
  return {