@opengeni/core 0.21.2 → 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 (123) hide show
  1. package/dist/access/index.d.ts +9 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +9 -4
  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 +35 -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/memory-slack-publication.d.ts +0 -2
  32. package/dist/domain/packs.d.ts +29 -1
  33. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  34. package/dist/domain/scheduled-tasks.d.ts +27 -3
  35. package/dist/domain/session-tool-policy.d.ts +2 -2
  36. package/dist/domain/sessions.d.ts +44 -10
  37. package/dist/domain/skill-imports.d.ts +32 -0
  38. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  39. package/dist/domain/timeline-annotations.d.ts +5 -0
  40. package/dist/domain/video-generation-capabilities.d.ts +9 -0
  41. package/dist/domain/video-generation.d.ts +47 -0
  42. package/dist/editable-artifact-live/application.d.ts +69 -0
  43. package/dist/editable-artifact-live/errors.d.ts +1 -0
  44. package/dist/editable-artifact-live/index.d.ts +8 -0
  45. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  46. package/dist/editable-artifact-live/ports.d.ts +100 -0
  47. package/dist/editable-artifact-live/server.d.ts +71 -0
  48. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  49. package/dist/editable-artifact-live/types.d.ts +213 -0
  50. package/dist/editable-artifact-live/wire.d.ts +34 -0
  51. package/dist/editable-artifact-live.d.ts +2 -0
  52. package/dist/editable-artifact-live.js +58 -0
  53. package/dist/editable-artifact-live.js.map +1 -0
  54. package/dist/editable-artifacts.d.ts +2 -0
  55. package/dist/editable-artifacts.js +179 -0
  56. package/dist/editable-artifacts.js.map +1 -0
  57. package/dist/index.d.ts +35 -25
  58. package/dist/index.js +3184 -652
  59. package/dist/index.js.map +1 -1
  60. package/dist/managed-session.d.ts +53 -0
  61. package/dist/rigs/index.d.ts +1 -1
  62. package/dist/rigs/provider-images.d.ts +31 -0
  63. package/dist/sandbox/fleet.d.ts +9 -1
  64. package/dist/sandbox/routing.d.ts +1 -0
  65. package/dist/session-authorization.d.ts +10 -1
  66. package/dist/workflow-wake-contract.d.ts +7 -0
  67. package/package.json +18 -10
  68. package/src/access/index.ts +43 -3
  69. package/src/application/new-session-drafts.ts +2 -0
  70. package/src/application/session-commands.ts +217 -116
  71. package/src/dependencies.ts +30 -2
  72. package/src/domain/capabilities.ts +672 -218
  73. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  74. package/src/domain/durable-learning-slack-publication.ts +202 -0
  75. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  76. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  77. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  78. package/src/domain/editable-artifacts/errors.ts +115 -0
  79. package/src/domain/editable-artifacts/hash.ts +131 -0
  80. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  81. package/src/domain/editable-artifacts/index.ts +14 -0
  82. package/src/domain/editable-artifacts/ports.ts +462 -0
  83. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  84. package/src/domain/editable-artifacts/service.ts +2402 -0
  85. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  86. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  87. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  88. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  89. package/src/domain/editable-artifacts/types.ts +835 -0
  90. package/src/domain/insights.ts +136 -33
  91. package/src/domain/memory-slack-delivery.ts +204 -0
  92. package/src/domain/memory-slack-publication.ts +10 -19
  93. package/src/domain/packs.ts +266 -5
  94. package/src/domain/personal-connection-delegations.ts +97 -6
  95. package/src/domain/resources.ts +10 -20
  96. package/src/domain/scheduled-tasks.ts +365 -25
  97. package/src/domain/session-tool-policy.ts +8 -2
  98. package/src/domain/sessions.ts +388 -155
  99. package/src/domain/skill-imports.ts +350 -0
  100. package/src/domain/slack-publication-secret-safety.ts +42 -0
  101. package/src/domain/timeline-annotations.ts +204 -0
  102. package/src/domain/video-generation-capabilities.ts +69 -0
  103. package/src/domain/video-generation.ts +249 -0
  104. package/src/editable-artifact-live/application.ts +182 -0
  105. package/src/editable-artifact-live/errors.ts +4 -0
  106. package/src/editable-artifact-live/index.ts +8 -0
  107. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  108. package/src/editable-artifact-live/ports.ts +139 -0
  109. package/src/editable-artifact-live/server.ts +1707 -0
  110. package/src/editable-artifact-live/ticket.ts +217 -0
  111. package/src/editable-artifact-live/types.ts +286 -0
  112. package/src/editable-artifact-live/wire.ts +95 -0
  113. package/src/editable-artifact-live.ts +2 -0
  114. package/src/editable-artifacts.ts +2 -0
  115. package/src/index.ts +14 -0
  116. package/src/managed-auth-type.ts +4 -3
  117. package/src/managed-session.ts +36 -0
  118. package/src/rigs/index.ts +26 -19
  119. package/src/rigs/provider-images.ts +110 -0
  120. package/src/sandbox/fleet.ts +56 -1
  121. package/src/sandbox/routing.ts +32 -3
  122. package/src/session-authorization.ts +20 -0
  123. 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
+ }
@@ -1,10 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import {
3
- redactSensitiveText,
4
3
  stableJson,
5
4
  type KnowledgeMemoryKind,
6
5
  type KnowledgeMemoryStatus,
7
6
  } from "@opengeni/contracts";
7
+ import { sanitizeSlackPublicationText } from "./slack-publication-secret-safety";
8
8
 
9
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}$/;
10
10
  const SELECTOR_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
@@ -109,13 +109,11 @@ export type MemorySlackPublicationProjection = {
109
109
  importance: MemorySlackImportance;
110
110
  deliveryMode: MemorySlackDeliveryMode;
111
111
  summary: string;
112
- summaryRedacted: boolean;
113
112
  summaryTruncated: boolean;
114
113
  namespace: string;
115
114
  labels: string[];
116
115
  labelsTruncated: boolean;
117
116
  ownerLabel: string | null;
118
- ownerLabelRedacted: boolean;
119
117
  ownerLabelTruncated: boolean;
120
118
  authoritativeRecord: {
121
119
  workspaceId: string;
@@ -200,10 +198,11 @@ export function evaluateMemorySlackPublication(
200
198
  const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
201
199
  if (!deliveryMode) return denied("below_noise_policy");
202
200
 
203
- const collapsedSummary = collapseText(input.distribution.shareSummary);
204
- const redactedSummary = redactSensitiveText(collapsedSummary);
205
- if (!redactedSummary) return denied("missing_summary");
206
- const summary = truncateUtf8(redactedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
201
+ const collapsedSummary = sanitizeSlackPublicationText(
202
+ collapseText(input.distribution.shareSummary),
203
+ );
204
+ if (!collapsedSummary) return denied("missing_summary");
205
+ const summary = truncateUtf8(collapsedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
207
206
 
208
207
  const namespace = normalizeNamespace(input.memory.namespace);
209
208
  const labels = normalizeLabels(input.memory.labels);
@@ -221,13 +220,11 @@ export function evaluateMemorySlackPublication(
221
220
  importance: input.distribution.importance,
222
221
  deliveryMode,
223
222
  summary: summary.value,
224
- summaryRedacted: redactedSummary !== collapsedSummary,
225
223
  summaryTruncated: summary.truncated,
226
224
  namespace,
227
225
  labels: labels.values,
228
226
  labelsTruncated: labels.truncated,
229
227
  ownerLabel: owner.value,
230
- ownerLabelRedacted: owner.redacted,
231
228
  ownerLabelTruncated: owner.truncated,
232
229
  authoritativeRecord: {
233
230
  workspaceId: input.context.workspaceId,
@@ -368,10 +365,8 @@ function effectiveDeliveryMode(
368
365
 
369
366
  function normalizeNamespace(value: string): string | null {
370
367
  const trimmed = value.trim();
371
- if (redactSensitiveText(trimmed) !== trimmed) return null;
372
368
  const namespace = trimmed.toLowerCase();
373
369
  if (!namespace || utf8Bytes(namespace) > MEMORY_SLACK_NAMESPACE_MAX_UTF8_BYTES) return null;
374
- if (redactSensitiveText(namespace) !== namespace) return null;
375
370
  const segments = namespace.split("/");
376
371
  if (segments.some((segment) => !SELECTOR_SEGMENT_PATTERN.test(segment))) return null;
377
372
  return segments.join("/");
@@ -385,10 +380,8 @@ function normalizeLabels(
385
380
  for (const value of values) {
386
381
  if (typeof value !== "string") return null;
387
382
  const trimmed = value.trim();
388
- if (redactSensitiveText(trimmed) !== trimmed) return null;
389
383
  const label = trimmed.toLowerCase();
390
384
  if (
391
- redactSensitiveText(label) !== label ||
392
385
  !SELECTOR_SEGMENT_PATTERN.test(label) ||
393
386
  utf8Bytes(label) > MEMORY_SLACK_LABEL_MAX_UTF8_BYTES
394
387
  ) {
@@ -406,14 +399,12 @@ function normalizeLabels(
406
399
  function boundedOptionalText(
407
400
  value: string | null | undefined,
408
401
  maxBytes: number,
409
- ): { value: string | null; redacted: boolean; truncated: boolean } {
410
- const collapsed = collapseText(value ?? "");
411
- if (!collapsed) return { value: null, redacted: false, truncated: false };
412
- const redacted = redactSensitiveText(collapsed);
413
- const bounded = truncateUtf8(redacted, maxBytes);
402
+ ): { value: string | null; truncated: boolean } {
403
+ const collapsed = sanitizeSlackPublicationText(collapseText(value ?? ""));
404
+ if (!collapsed) return { value: null, truncated: false };
405
+ const bounded = truncateUtf8(collapsed, maxBytes);
414
406
  return {
415
407
  value: bounded.value || null,
416
- redacted: redacted !== collapsed,
417
408
  truncated: bounded.truncated,
418
409
  };
419
410
  }