iranti 0.2.51 → 0.3.2

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 (163) hide show
  1. package/README.md +30 -17
  2. package/dist/scripts/api-key-create.js +1 -1
  3. package/dist/scripts/api-key-list.js +1 -1
  4. package/dist/scripts/api-key-revoke.js +1 -1
  5. package/dist/scripts/claude-code-memory-hook.js +116 -30
  6. package/dist/scripts/codex-setup.js +86 -4
  7. package/dist/scripts/iranti-cli.js +1359 -57
  8. package/dist/scripts/iranti-mcp.js +578 -75
  9. package/dist/scripts/seed.js +11 -6
  10. package/dist/scripts/setup.js +1 -1
  11. package/dist/src/api/healthChecks.d.ts +29 -0
  12. package/dist/src/api/healthChecks.d.ts.map +1 -0
  13. package/dist/src/api/healthChecks.js +72 -0
  14. package/dist/src/api/healthChecks.js.map +1 -0
  15. package/dist/src/api/middleware/validation.d.ts +22 -0
  16. package/dist/src/api/middleware/validation.d.ts.map +1 -1
  17. package/dist/src/api/middleware/validation.js +93 -3
  18. package/dist/src/api/middleware/validation.js.map +1 -1
  19. package/dist/src/api/routes/knowledge.d.ts.map +1 -1
  20. package/dist/src/api/routes/knowledge.js +53 -0
  21. package/dist/src/api/routes/knowledge.js.map +1 -1
  22. package/dist/src/api/routes/memory.d.ts.map +1 -1
  23. package/dist/src/api/routes/memory.js +73 -9
  24. package/dist/src/api/routes/memory.js.map +1 -1
  25. package/dist/src/api/server.js +38 -43
  26. package/dist/src/api/server.js.map +1 -1
  27. package/dist/src/attendant/AttendantInstance.d.ts +135 -2
  28. package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
  29. package/dist/src/attendant/AttendantInstance.js +1836 -93
  30. package/dist/src/attendant/AttendantInstance.js.map +1 -1
  31. package/dist/src/attendant/index.d.ts +1 -1
  32. package/dist/src/attendant/index.d.ts.map +1 -1
  33. package/dist/src/attendant/index.js +1 -1
  34. package/dist/src/attendant/index.js.map +1 -1
  35. package/dist/src/attendant/registry.d.ts.map +1 -1
  36. package/dist/src/attendant/registry.js +2 -0
  37. package/dist/src/attendant/registry.js.map +1 -1
  38. package/dist/src/chat/index.d.ts +23 -0
  39. package/dist/src/chat/index.d.ts.map +1 -1
  40. package/dist/src/chat/index.js +111 -22
  41. package/dist/src/chat/index.js.map +1 -1
  42. package/dist/src/generated/prisma/browser.d.ts +5 -0
  43. package/dist/src/generated/prisma/browser.d.ts.map +1 -1
  44. package/dist/src/generated/prisma/client.d.ts +5 -0
  45. package/dist/src/generated/prisma/client.d.ts.map +1 -1
  46. package/dist/src/generated/prisma/commonInputTypes.d.ts +48 -0
  47. package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -1
  48. package/dist/src/generated/prisma/internal/class.d.ts +11 -0
  49. package/dist/src/generated/prisma/internal/class.d.ts.map +1 -1
  50. package/dist/src/generated/prisma/internal/class.js +4 -4
  51. package/dist/src/generated/prisma/internal/class.js.map +1 -1
  52. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +92 -1
  53. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -1
  54. package/dist/src/generated/prisma/internal/prismaNamespace.js +17 -2
  55. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -1
  56. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +16 -0
  57. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  58. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +17 -2
  59. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -1
  60. package/dist/src/generated/prisma/models/StaffEvent.d.ts +1184 -0
  61. package/dist/src/generated/prisma/models/StaffEvent.d.ts.map +1 -0
  62. package/dist/src/generated/prisma/models/StaffEvent.js +3 -0
  63. package/dist/src/generated/prisma/models/StaffEvent.js.map +1 -0
  64. package/dist/src/generated/prisma/models.d.ts +1 -0
  65. package/dist/src/generated/prisma/models.d.ts.map +1 -1
  66. package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
  67. package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
  68. package/dist/src/lib/assistantCheckpoint.js +143 -0
  69. package/dist/src/lib/assistantCheckpoint.js.map +1 -0
  70. package/dist/src/lib/autoRemember.d.ts +15 -0
  71. package/dist/src/lib/autoRemember.d.ts.map +1 -1
  72. package/dist/src/lib/autoRemember.js +433 -71
  73. package/dist/src/lib/autoRemember.js.map +1 -1
  74. package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
  75. package/dist/src/lib/cliHelpCatalog.js +23 -11
  76. package/dist/src/lib/cliHelpCatalog.js.map +1 -1
  77. package/dist/src/lib/cliHelpRenderer.d.ts +1 -0
  78. package/dist/src/lib/cliHelpRenderer.d.ts.map +1 -1
  79. package/dist/src/lib/cliHelpRenderer.js +4 -0
  80. package/dist/src/lib/cliHelpRenderer.js.map +1 -1
  81. package/dist/src/lib/commandErrors.d.ts +5 -1
  82. package/dist/src/lib/commandErrors.d.ts.map +1 -1
  83. package/dist/src/lib/commandErrors.js +250 -17
  84. package/dist/src/lib/commandErrors.js.map +1 -1
  85. package/dist/src/lib/createFirstPartyIranti.d.ts.map +1 -1
  86. package/dist/src/lib/createFirstPartyIranti.js +1 -0
  87. package/dist/src/lib/createFirstPartyIranti.js.map +1 -1
  88. package/dist/src/lib/dbStaffEventEmitter.d.ts +2 -0
  89. package/dist/src/lib/dbStaffEventEmitter.d.ts.map +1 -1
  90. package/dist/src/lib/dbStaffEventEmitter.js +15 -0
  91. package/dist/src/lib/dbStaffEventEmitter.js.map +1 -1
  92. package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
  93. package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
  94. package/dist/src/lib/hostMemoryFormatting.js +55 -0
  95. package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
  96. package/dist/src/lib/issueFacts.d.ts +37 -0
  97. package/dist/src/lib/issueFacts.d.ts.map +1 -0
  98. package/dist/src/lib/issueFacts.js +72 -0
  99. package/dist/src/lib/issueFacts.js.map +1 -0
  100. package/dist/src/lib/llm.d.ts +8 -0
  101. package/dist/src/lib/llm.d.ts.map +1 -1
  102. package/dist/src/lib/llm.js +33 -0
  103. package/dist/src/lib/llm.js.map +1 -1
  104. package/dist/src/lib/packageRoot.d.ts +2 -0
  105. package/dist/src/lib/packageRoot.d.ts.map +1 -0
  106. package/dist/src/lib/packageRoot.js +22 -0
  107. package/dist/src/lib/packageRoot.js.map +1 -0
  108. package/dist/src/lib/projectLearning.d.ts +21 -0
  109. package/dist/src/lib/projectLearning.d.ts.map +1 -0
  110. package/dist/src/lib/projectLearning.js +357 -0
  111. package/dist/src/lib/projectLearning.js.map +1 -0
  112. package/dist/src/lib/protocolEnforcement.d.ts +29 -0
  113. package/dist/src/lib/protocolEnforcement.d.ts.map +1 -0
  114. package/dist/src/lib/protocolEnforcement.js +124 -0
  115. package/dist/src/lib/protocolEnforcement.js.map +1 -0
  116. package/dist/src/lib/providers/claude.js +1 -1
  117. package/dist/src/lib/providers/claude.js.map +1 -1
  118. package/dist/src/lib/router.js +1 -1
  119. package/dist/src/lib/router.js.map +1 -1
  120. package/dist/src/lib/runtimeEnv.d.ts.map +1 -1
  121. package/dist/src/lib/runtimeEnv.js +8 -3
  122. package/dist/src/lib/runtimeEnv.js.map +1 -1
  123. package/dist/src/lib/scaffoldCloseout.d.ts +27 -0
  124. package/dist/src/lib/scaffoldCloseout.d.ts.map +1 -0
  125. package/dist/src/lib/scaffoldCloseout.js +139 -0
  126. package/dist/src/lib/scaffoldCloseout.js.map +1 -0
  127. package/dist/src/lib/semanticFactTags.d.ts +10 -0
  128. package/dist/src/lib/semanticFactTags.d.ts.map +1 -0
  129. package/dist/src/lib/semanticFactTags.js +166 -0
  130. package/dist/src/lib/semanticFactTags.js.map +1 -0
  131. package/dist/src/lib/sessionLedger.d.ts +94 -0
  132. package/dist/src/lib/sessionLedger.d.ts.map +1 -0
  133. package/dist/src/lib/sessionLedger.js +997 -0
  134. package/dist/src/lib/sessionLedger.js.map +1 -0
  135. package/dist/src/lib/sharedStateInvalidation.d.ts +10 -0
  136. package/dist/src/lib/sharedStateInvalidation.d.ts.map +1 -0
  137. package/dist/src/lib/sharedStateInvalidation.js +184 -0
  138. package/dist/src/lib/sharedStateInvalidation.js.map +1 -0
  139. package/dist/src/lib/staffEventsTable.d.ts +3 -0
  140. package/dist/src/lib/staffEventsTable.d.ts.map +1 -0
  141. package/dist/src/lib/staffEventsTable.js +58 -0
  142. package/dist/src/lib/staffEventsTable.js.map +1 -0
  143. package/dist/src/librarian/index.d.ts.map +1 -1
  144. package/dist/src/librarian/index.js +113 -2
  145. package/dist/src/librarian/index.js.map +1 -1
  146. package/dist/src/library/client.d.ts +6 -1
  147. package/dist/src/library/client.d.ts.map +1 -1
  148. package/dist/src/library/client.js +21 -7
  149. package/dist/src/library/client.js.map +1 -1
  150. package/dist/src/library/embeddings.d.ts +9 -1
  151. package/dist/src/library/embeddings.d.ts.map +1 -1
  152. package/dist/src/library/embeddings.js +28 -3
  153. package/dist/src/library/embeddings.js.map +1 -1
  154. package/dist/src/library/queries.d.ts.map +1 -1
  155. package/dist/src/library/queries.js +263 -46
  156. package/dist/src/library/queries.js.map +1 -1
  157. package/dist/src/sdk/index.d.ts +52 -1
  158. package/dist/src/sdk/index.d.ts.map +1 -1
  159. package/dist/src/sdk/index.js +546 -98
  160. package/dist/src/sdk/index.js.map +1 -1
  161. package/package.json +24 -3
  162. package/prisma/migrations/20260331101500_add_staff_events_ledger/migration.sql +24 -0
  163. package/prisma/schema.prisma +22 -0
@@ -0,0 +1,997 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionLedgerUnavailableError = void 0;
4
+ exports.querySessionLedger = querySessionLedger;
5
+ exports.summarizeSessionLedgerLearnings = summarizeSessionLedgerLearnings;
6
+ exports.summarizeMemoryAttribution = summarizeMemoryAttribution;
7
+ exports.buildSessionLedgerLearningProfile = buildSessionLedgerLearningProfile;
8
+ const client_1 = require("../generated/prisma/client");
9
+ const client_2 = require("../library/client");
10
+ const staffEventsTable_1 = require("./staffEventsTable");
11
+ class SessionLedgerUnavailableError extends Error {
12
+ constructor(message = 'staff_events table is missing. Create it before querying the session ledger.') {
13
+ super(message);
14
+ this.code = 'SESSION_LEDGER_UNAVAILABLE';
15
+ this.name = 'SessionLedgerUnavailableError';
16
+ }
17
+ }
18
+ exports.SessionLedgerUnavailableError = SessionLedgerUnavailableError;
19
+ function isMissingStaffEventsTable(error) {
20
+ const message = error instanceof Error ? error.message : String(error);
21
+ return /relation ["']?staff_events["']? does not exist/i.test(message);
22
+ }
23
+ function normalizeLimit(limit) {
24
+ if (!Number.isFinite(limit))
25
+ return 100;
26
+ return Math.max(1, Math.min(500, Math.floor(limit)));
27
+ }
28
+ function normalizeLearningLimit(limit) {
29
+ if (!Number.isFinite(limit))
30
+ return 4;
31
+ return Math.max(1, Math.min(10, Math.floor(limit)));
32
+ }
33
+ function normalizeProfileLimit(limit) {
34
+ if (!Number.isFinite(limit))
35
+ return 60;
36
+ return Math.max(10, Math.min(200, Math.floor(limit)));
37
+ }
38
+ function rowToEvent(row) {
39
+ return {
40
+ eventId: row.event_id,
41
+ timestamp: row.timestamp instanceof Date ? row.timestamp.toISOString() : String(row.timestamp),
42
+ staffComponent: row.staff_component,
43
+ actionType: row.action_type,
44
+ agentId: row.agent_id,
45
+ source: row.source,
46
+ entityType: row.entity_type ?? null,
47
+ entityId: row.entity_id ?? null,
48
+ key: row.key ?? null,
49
+ reason: row.reason ?? null,
50
+ level: row.level,
51
+ metadata: row.metadata ?? null,
52
+ };
53
+ }
54
+ async function querySessionLedger(input = {}) {
55
+ const clauses = [];
56
+ if (input.agentId?.trim()) {
57
+ clauses.push(client_1.Prisma.sql `agent_id = ${input.agentId.trim()}`);
58
+ }
59
+ if (input.sessionId?.trim()) {
60
+ clauses.push(client_1.Prisma.sql `metadata->>'sessionId' = ${input.sessionId.trim()}`);
61
+ }
62
+ if (input.actionType?.trim()) {
63
+ clauses.push(client_1.Prisma.sql `action_type = ${input.actionType.trim()}`);
64
+ }
65
+ if (Array.isArray(input.actionTypes)) {
66
+ const actionTypes = input.actionTypes
67
+ .map((value) => value.trim())
68
+ .filter(Boolean);
69
+ if (actionTypes.length > 0) {
70
+ clauses.push(client_1.Prisma.sql `action_type IN (${client_1.Prisma.join(actionTypes)})`);
71
+ }
72
+ }
73
+ if (input.source?.trim()) {
74
+ clauses.push(client_1.Prisma.sql `source = ${input.source.trim()}`);
75
+ }
76
+ if (input.host?.trim()) {
77
+ clauses.push(client_1.Prisma.sql `metadata->>'host' = ${input.host.trim()}`);
78
+ }
79
+ if (input.level) {
80
+ clauses.push(client_1.Prisma.sql `level = ${input.level}`);
81
+ }
82
+ if (input.since) {
83
+ clauses.push(client_1.Prisma.sql `timestamp > ${input.since.toISOString()}::timestamptz`);
84
+ }
85
+ if (input.until) {
86
+ clauses.push(client_1.Prisma.sql `timestamp <= ${input.until.toISOString()}::timestamptz`);
87
+ }
88
+ const whereClause = clauses.length > 0
89
+ ? client_1.Prisma.sql `WHERE ${client_1.Prisma.join(clauses, ' AND ')}`
90
+ : client_1.Prisma.empty;
91
+ try {
92
+ const rows = await runLedgerQuery(whereClause, normalizeLimit(input.limit));
93
+ return rows.map(rowToEvent);
94
+ }
95
+ catch (error) {
96
+ if (isMissingStaffEventsTable(error)) {
97
+ try {
98
+ await (0, staffEventsTable_1.ensureStaffEventsTable)();
99
+ const rows = await runLedgerQuery(whereClause, normalizeLimit(input.limit));
100
+ return rows.map(rowToEvent);
101
+ }
102
+ catch (ensureError) {
103
+ if (isMissingStaffEventsTable(ensureError)) {
104
+ throw new SessionLedgerUnavailableError();
105
+ }
106
+ throw ensureError;
107
+ }
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+ async function runLedgerQuery(whereClause, limit) {
113
+ return (0, client_2.getDb)().$queryRaw(client_1.Prisma.sql `
114
+ SELECT
115
+ event_id,
116
+ timestamp,
117
+ staff_component,
118
+ action_type,
119
+ agent_id,
120
+ source,
121
+ entity_type,
122
+ entity_id,
123
+ key,
124
+ reason,
125
+ level,
126
+ metadata
127
+ FROM staff_events
128
+ ${whereClause}
129
+ ORDER BY timestamp DESC
130
+ LIMIT ${limit}
131
+ `);
132
+ }
133
+ const LEDGER_LEARNING_ACTIONS = new Set([
134
+ 'host_failure',
135
+ 'provider_fallback_used',
136
+ 'integration_probe_failed',
137
+ 'mandatory_recall_forced',
138
+ 'memory_injected',
139
+ 'memory_evidence_observed',
140
+ 'memory_injection_scored',
141
+ 'query_executed',
142
+ 'search_executed',
143
+ 'related_executed',
144
+ 'related_deep_executed',
145
+ 'whoknows_executed',
146
+ 'checkpoint_written',
147
+ 'checkpoint_availability_failed',
148
+ 'summary_written',
149
+ 'write_available',
150
+ 'write_availability_failed',
151
+ 'write_rejected',
152
+ 'write_escalated',
153
+ 'write_updated',
154
+ 'write_replaced',
155
+ 'write_created',
156
+ 'checkpoint_shared_breadcrumb_failed',
157
+ ]);
158
+ const LEDGER_SUCCESS_ACTIONS = new Set([
159
+ 'memory_injected',
160
+ 'memory_injection_scored',
161
+ 'query_executed',
162
+ 'search_executed',
163
+ 'related_executed',
164
+ 'related_deep_executed',
165
+ 'whoknows_executed',
166
+ 'checkpoint_written',
167
+ 'write_available',
168
+ 'summary_written',
169
+ 'write_created',
170
+ 'write_updated',
171
+ 'write_replaced',
172
+ ]);
173
+ const LEDGER_ACTIVITY_ACTIONS = new Set([
174
+ 'query_executed',
175
+ 'search_executed',
176
+ 'related_executed',
177
+ 'related_deep_executed',
178
+ 'whoknows_executed',
179
+ 'observe_completed',
180
+ 'attend_completed',
181
+ 'memory_injected',
182
+ 'memory_not_injected',
183
+ 'memory_evidence_observed',
184
+ 'memory_injection_scored',
185
+ 'mandatory_recall_forced',
186
+ ]);
187
+ const LEDGER_PERSISTENCE_ACTIONS = new Set([
188
+ 'checkpoint_written',
189
+ 'write_available',
190
+ 'summary_written',
191
+ 'write_created',
192
+ 'write_updated',
193
+ 'write_replaced',
194
+ ]);
195
+ const LEDGER_FAILURE_ACTIONS = new Set([
196
+ 'host_failure',
197
+ 'integration_probe_failed',
198
+ 'provider_fallback_used',
199
+ 'checkpoint_availability_failed',
200
+ 'write_availability_failed',
201
+ 'write_rejected',
202
+ 'write_escalated',
203
+ 'checkpoint_shared_breadcrumb_failed',
204
+ ]);
205
+ const LEDGER_DISCOVERY_ACTIONS = new Set([
206
+ 'query_executed',
207
+ 'search_executed',
208
+ 'related_executed',
209
+ 'related_deep_executed',
210
+ 'whoknows_executed',
211
+ 'observe_completed',
212
+ 'memory_injected',
213
+ 'memory_evidence_observed',
214
+ 'memory_injection_scored',
215
+ 'mandatory_recall_forced',
216
+ ]);
217
+ const LEDGER_REQUIRED_WRITE_CATEGORY_LABELS = {
218
+ findings: 'what you found',
219
+ validated_results: 'what worked',
220
+ failed_paths: 'what failed',
221
+ file_changes: 'what changed',
222
+ risks_and_next_steps: 'what remains risky and what happens next',
223
+ };
224
+ const TASK_TOKEN_STOPWORDS = new Set([
225
+ 'the',
226
+ 'and',
227
+ 'for',
228
+ 'with',
229
+ 'from',
230
+ 'into',
231
+ 'that',
232
+ 'this',
233
+ 'what',
234
+ 'when',
235
+ 'where',
236
+ 'which',
237
+ 'while',
238
+ 'about',
239
+ 'need',
240
+ 'needs',
241
+ 'work',
242
+ 'working',
243
+ 'task',
244
+ 'tasks',
245
+ 'project',
246
+ 'agent',
247
+ 'session',
248
+ 'continue',
249
+ 'continuing',
250
+ ]);
251
+ // Maps common engineering task vocabulary to canonical forms so that synonymous
252
+ // task descriptions score well even when they share no raw tokens.
253
+ // Keys must be 4+ characters (matching the tokenizeTaskText length filter).
254
+ const TASK_TOKEN_SYNONYMS = {
255
+ // Bug resolution
256
+ 'fixing': 'repair',
257
+ 'fixed': 'repair',
258
+ 'resolve': 'repair',
259
+ 'resolving': 'repair',
260
+ 'resolved': 'repair',
261
+ 'debug': 'repair',
262
+ 'debugging': 'repair',
263
+ 'debugged': 'repair',
264
+ 'patch': 'repair',
265
+ 'patching': 'repair',
266
+ 'repair': 'repair',
267
+ 'repairing': 'repair',
268
+ 'troubleshoot': 'repair',
269
+ 'troubleshooting': 'repair',
270
+ // Container / Docker
271
+ 'docker': 'container',
272
+ 'container': 'container',
273
+ 'containers': 'container',
274
+ // Startup / init / launch
275
+ 'startup': 'startup',
276
+ 'boot': 'startup',
277
+ 'init': 'startup',
278
+ 'initialize': 'startup',
279
+ 'initialise': 'startup',
280
+ 'initializing': 'startup',
281
+ 'launch': 'startup',
282
+ 'launching': 'startup',
283
+ // Authentication
284
+ 'auth': 'auth',
285
+ 'authentication': 'auth',
286
+ 'authenticate': 'auth',
287
+ 'login': 'auth',
288
+ 'signin': 'auth',
289
+ 'authorization': 'auth',
290
+ 'authorize': 'auth',
291
+ // Database
292
+ 'database': 'database',
293
+ 'postgres': 'database',
294
+ 'postgresql': 'database',
295
+ 'prisma': 'database',
296
+ // Errors / failures
297
+ 'error': 'error',
298
+ 'errors': 'error',
299
+ 'bugs': 'error',
300
+ 'failure': 'error',
301
+ 'failures': 'error',
302
+ 'problem': 'error',
303
+ 'problems': 'error',
304
+ 'issue': 'error',
305
+ 'issues': 'error',
306
+ 'fault': 'error',
307
+ // Memory / injection (Iranti-domain)
308
+ 'memory': 'memory',
309
+ 'injection': 'memory',
310
+ 'inject': 'memory',
311
+ 'injecting': 'memory',
312
+ // Performance
313
+ 'performance': 'performance',
314
+ 'optimize': 'performance',
315
+ 'optimise': 'performance',
316
+ 'optimizing': 'performance',
317
+ 'latency': 'performance',
318
+ 'slow': 'performance',
319
+ 'speed': 'performance',
320
+ };
321
+ function describeEvent(event) {
322
+ const host = typeof event.metadata?.host === 'string' ? event.metadata.host : null;
323
+ const entityKey = event.entityType && event.entityId && event.key
324
+ ? `${event.entityType}/${event.entityId}/${event.key}`
325
+ : event.entityType && event.entityId
326
+ ? `${event.entityType}/${event.entityId}`
327
+ : null;
328
+ const error = typeof event.metadata?.error === 'string' ? event.metadata.error : null;
329
+ const injectedKeys = Array.isArray(event.metadata?.injectedKeys)
330
+ ? event.metadata.injectedKeys.map((value) => String(value)).filter(Boolean)
331
+ : [];
332
+ switch (event.actionType) {
333
+ case 'host_failure':
334
+ case 'integration_probe_failed':
335
+ case 'provider_fallback_used':
336
+ return `${host ?? event.source} failure: ${event.reason ?? error ?? 'host error'}`;
337
+ case 'mandatory_recall_forced':
338
+ return event.key
339
+ ? `recall policy forced a lookup for ${event.key}`
340
+ : 'recall policy forced a memory lookup';
341
+ case 'memory_injected':
342
+ return injectedKeys.length > 0
343
+ ? `memory injected from ${host ?? event.source}: ${injectedKeys.slice(0, 3).join(', ')}`
344
+ : `memory injected from ${host ?? event.source}`;
345
+ case 'memory_evidence_observed': {
346
+ const evidenceKind = typeof event.metadata?.evidenceKind === 'string' ? event.metadata.evidenceKind : 'memory_evidence';
347
+ return `memory evidence observed from ${host ?? event.source}: ${evidenceKind}`;
348
+ }
349
+ case 'memory_injection_scored': {
350
+ const used = event.metadata?.used === true;
351
+ const helpful = event.metadata?.helpful === true;
352
+ return `memory injection scored from ${host ?? event.source}: used=${used} helpful=${helpful}`;
353
+ }
354
+ case 'checkpoint_written':
355
+ return entityKey
356
+ ? `shared checkpoint written for ${entityKey}`
357
+ : 'shared checkpoint written';
358
+ case 'checkpoint_availability_failed':
359
+ return entityKey
360
+ ? `checkpoint availability failed for ${entityKey}`
361
+ : `checkpoint availability failed${error ? `: ${error}` : ''}`;
362
+ case 'summary_written':
363
+ return entityKey
364
+ ? `strict summary written for ${entityKey}`
365
+ : 'strict summary written';
366
+ case 'checkpoint_shared_breadcrumb_failed':
367
+ return entityKey
368
+ ? `shared checkpoint breadcrumb failed for ${entityKey}`
369
+ : `shared checkpoint breadcrumb failed${error ? `: ${error}` : ''}`;
370
+ case 'write_created':
371
+ return entityKey ? `created ${entityKey}` : 'created durable fact';
372
+ case 'write_available':
373
+ return entityKey ? `write availability verified for ${entityKey}` : 'write availability verified';
374
+ case 'write_availability_failed':
375
+ return entityKey
376
+ ? `write availability failed for ${entityKey}`
377
+ : `write availability failed${error ? `: ${error}` : ''}`;
378
+ case 'write_updated':
379
+ return entityKey ? `updated ${entityKey}` : 'updated durable fact';
380
+ case 'write_replaced':
381
+ return entityKey ? `replaced ${entityKey}` : 'replaced durable fact';
382
+ case 'write_rejected':
383
+ return entityKey
384
+ ? `rejected write for ${entityKey}${event.reason ? `: ${event.reason}` : ''}`
385
+ : 'rejected write';
386
+ case 'write_escalated':
387
+ return entityKey
388
+ ? `escalated conflict for ${entityKey}`
389
+ : 'escalated write conflict';
390
+ default:
391
+ return null;
392
+ }
393
+ }
394
+ function eventToLearning(event) {
395
+ if (!LEDGER_LEARNING_ACTIONS.has(event.actionType)) {
396
+ return null;
397
+ }
398
+ const summary = describeEvent(event);
399
+ if (!summary)
400
+ return null;
401
+ return {
402
+ actionType: event.actionType,
403
+ summary,
404
+ timestamp: event.timestamp,
405
+ source: event.source,
406
+ host: typeof event.metadata?.host === 'string' ? event.metadata.host : null,
407
+ sessionId: typeof event.metadata?.sessionId === 'string' ? event.metadata.sessionId : null,
408
+ entityKey: event.entityType && event.entityId && event.key ? `${event.entityType}/${event.entityId}/${event.key}` : null,
409
+ reason: event.reason ?? null,
410
+ category: 'event',
411
+ evidenceActionTypes: [event.actionType],
412
+ };
413
+ }
414
+ function latestTimestamp(events) {
415
+ return events
416
+ .map((event) => event.timestamp)
417
+ .sort((left, right) => right.localeCompare(left))[0] ?? new Date(0).toISOString();
418
+ }
419
+ function distinct(values) {
420
+ return Array.from(new Set(values));
421
+ }
422
+ function inferMissingWriteCategories(sessionEvents) {
423
+ const categories = new Set();
424
+ const activityCount = sessionEvents.filter((event) => LEDGER_ACTIVITY_ACTIONS.has(event.actionType)).length;
425
+ const discoveryCount = sessionEvents.filter((event) => LEDGER_DISCOVERY_ACTIONS.has(event.actionType)).length;
426
+ const failureCount = sessionEvents.filter((event) => LEDGER_FAILURE_ACTIONS.has(event.actionType)).length;
427
+ const successCount = sessionEvents.filter((event) => LEDGER_SUCCESS_ACTIONS.has(event.actionType)).length;
428
+ if (discoveryCount > 0 || activityCount >= 4) {
429
+ categories.add('findings');
430
+ categories.add('risks_and_next_steps');
431
+ }
432
+ if (successCount > 0) {
433
+ categories.add('validated_results');
434
+ }
435
+ if (failureCount > 0) {
436
+ categories.add('failed_paths');
437
+ }
438
+ if (activityCount >= 8) {
439
+ categories.add('file_changes');
440
+ }
441
+ return Array.from(categories);
442
+ }
443
+ function formatWriteCategoryList(categories) {
444
+ const labels = categories
445
+ .map((category) => LEDGER_REQUIRED_WRITE_CATEGORY_LABELS[category] ?? category)
446
+ .filter(Boolean);
447
+ if (labels.length === 0) {
448
+ return 'what you found and what happens next';
449
+ }
450
+ if (labels.length === 1)
451
+ return labels[0];
452
+ if (labels.length === 2)
453
+ return `${labels[0]} and ${labels[1]}`;
454
+ return `${labels.slice(0, -1).join(', ')}, and ${labels[labels.length - 1]}`;
455
+ }
456
+ function tokenizeTaskText(text) {
457
+ if (!text)
458
+ return [];
459
+ return distinct(text
460
+ .toLowerCase()
461
+ .split(/[^a-z0-9_]+/)
462
+ .map((token) => token.trim())
463
+ .filter((token) => token.length >= 4 && !TASK_TOKEN_STOPWORDS.has(token)));
464
+ }
465
+ function expandTokenSynonym(token) {
466
+ return TASK_TOKEN_SYNONYMS[token] ?? token;
467
+ }
468
+ function scoreTaskSimilarity(left, right) {
469
+ const leftTokens = tokenizeTaskText(left);
470
+ const rightTokens = tokenizeTaskText(right);
471
+ if (leftTokens.length === 0 || rightTokens.length === 0)
472
+ return 0;
473
+ // Raw Jaccard on original tokens.
474
+ const leftSet = new Set(leftTokens);
475
+ const rightSet = new Set(rightTokens);
476
+ const overlap = leftTokens.filter((token) => rightSet.has(token)).length;
477
+ const union = new Set([...leftSet, ...rightSet]).size;
478
+ const rawScore = union === 0 ? 0 : overlap / union;
479
+ // Synonym-expanded Jaccard: canonicalize each token then recompute.
480
+ // This catches synonymous task descriptions that share no raw tokens
481
+ // (e.g. "fix docker bug" vs "repair container startup issue").
482
+ const leftExpanded = distinct(leftTokens.map(expandTokenSynonym));
483
+ const rightExpanded = distinct(rightTokens.map(expandTokenSynonym));
484
+ const leftExpandedSet = new Set(leftExpanded);
485
+ const rightExpandedSet = new Set(rightExpanded);
486
+ const expandedOverlap = leftExpanded.filter((token) => rightExpandedSet.has(token)).length;
487
+ const expandedUnion = new Set([...leftExpandedSet, ...rightExpandedSet]).size;
488
+ const expandedScore = expandedUnion === 0 ? 0 : expandedOverlap / expandedUnion;
489
+ return Math.max(rawScore, expandedScore);
490
+ }
491
+ function taskSummaryForEvent(event) {
492
+ const directTask = typeof event.metadata?.task === 'string' ? event.metadata.task : null;
493
+ const summarizedTask = typeof event.metadata?.taskSummary === 'string' ? event.metadata.taskSummary : null;
494
+ return directTask ?? summarizedTask ?? null;
495
+ }
496
+ function scopeEventsByTaskType(events, taskType) {
497
+ if (!taskType?.trim()) {
498
+ return {
499
+ matchedTaskType: null,
500
+ events: [],
501
+ };
502
+ }
503
+ const scored = events
504
+ .map((event) => ({
505
+ event,
506
+ taskSummary: taskSummaryForEvent(event),
507
+ score: scoreTaskSimilarity(taskSummaryForEvent(event), taskType),
508
+ }))
509
+ .filter((entry) => entry.taskSummary && entry.score >= 0.34)
510
+ .sort((left, right) => right.score - left.score);
511
+ if (scored.length === 0) {
512
+ return {
513
+ matchedTaskType: null,
514
+ events: [],
515
+ };
516
+ }
517
+ const matchingSessionIds = new Set(scored
518
+ .map((entry) => typeof entry.event.metadata?.sessionId === 'string' ? entry.event.metadata.sessionId : null)
519
+ .filter((value) => Boolean(value)));
520
+ const scoped = matchingSessionIds.size === 0
521
+ ? scored.map((entry) => entry.event)
522
+ : events.filter((event) => {
523
+ const sessionId = typeof event.metadata?.sessionId === 'string' ? event.metadata.sessionId : null;
524
+ return sessionId ? matchingSessionIds.has(sessionId) : false;
525
+ });
526
+ return {
527
+ matchedTaskType: scored[0]?.taskSummary ?? null,
528
+ events: scoped,
529
+ };
530
+ }
531
+ function extractEventEntityKeys(event) {
532
+ const directEntityKey = event.entityType && event.entityId && event.key
533
+ ? [`${event.entityType}/${event.entityId}/${event.key}`]
534
+ : [];
535
+ const injectedKeys = Array.isArray(event.metadata?.injectedKeys)
536
+ ? event.metadata.injectedKeys.map((value) => String(value)).filter(Boolean)
537
+ : [];
538
+ return distinct([...directEntityKey, ...injectedKeys]);
539
+ }
540
+ function buildPriorityKeys(events) {
541
+ const counts = new Map();
542
+ const lastSeen = new Map();
543
+ for (const event of events) {
544
+ if (!LEDGER_SUCCESS_ACTIONS.has(event.actionType) && event.actionType !== 'mandatory_recall_forced') {
545
+ continue;
546
+ }
547
+ for (const entityKey of extractEventEntityKeys(event)) {
548
+ const parts = entityKey.split('/');
549
+ const key = parts.length >= 3 ? parts.slice(2).join('/') : null;
550
+ if (!key || key === 'attendant_state')
551
+ continue;
552
+ counts.set(key, (counts.get(key) ?? 0) + 1);
553
+ lastSeen.set(key, event.timestamp);
554
+ }
555
+ }
556
+ return Array.from(counts.entries())
557
+ .sort((left, right) => ((right[1] - left[1])
558
+ || (lastSeen.get(right[0]) ?? '').localeCompare(lastSeen.get(left[0]) ?? '')
559
+ || left[0].localeCompare(right[0])))
560
+ .slice(0, 6)
561
+ .map(([key]) => key);
562
+ }
563
+ function scopeSummaries(events) {
564
+ return [
565
+ ...buildHostLearnings(events),
566
+ ...buildRecallLearnings(events),
567
+ ...buildPersistenceLearnings(events),
568
+ ...buildComplianceLearnings(events),
569
+ ]
570
+ .sort((left, right) => right.timestamp.localeCompare(left.timestamp))
571
+ .map((learning) => learning.summary)
572
+ .slice(0, 2);
573
+ }
574
+ function scopeSupportsAmbiguousMemory(events) {
575
+ // Strongest signal: a memory_injected event followed by a persistence event
576
+ // in the same session — the agent received injected context and then wrote
577
+ // something durable, indicating the injection was productive.
578
+ const sessions = new Map();
579
+ for (const event of events) {
580
+ const sessionId = typeof event.metadata?.sessionId === 'string' ? event.metadata.sessionId : '_unknown';
581
+ const bucket = sessions.get(sessionId) ?? [];
582
+ bucket.push(event);
583
+ sessions.set(sessionId, bucket);
584
+ }
585
+ for (const sessionEvents of sessions.values()) {
586
+ const sorted = sessionEvents.slice().sort((a, b) => a.timestamp.localeCompare(b.timestamp));
587
+ let seenInjection = false;
588
+ for (const event of sorted) {
589
+ if (event.actionType === 'memory_injected') {
590
+ seenInjection = true;
591
+ }
592
+ else if (seenInjection && LEDGER_PERSISTENCE_ACTIONS.has(event.actionType)) {
593
+ return true;
594
+ }
595
+ }
596
+ }
597
+ // Advisory-first fallback: injection was used in this scope even if no downstream
598
+ // persistence was confirmed — the pattern still signals this host uses injection.
599
+ if (events.some((event) => event.actionType === 'memory_injected')) {
600
+ return true;
601
+ }
602
+ // No injection history: fall back to whether the scope produced any durable
603
+ // output at all. Retrieval-only sessions (query/search without writes) do not
604
+ // qualify — they carry no signal that ambiguous injection would be useful.
605
+ return events.some((event) => LEDGER_PERSISTENCE_ACTIONS.has(event.actionType));
606
+ }
607
+ function buildComplianceLearnings(events) {
608
+ const sessions = new Map();
609
+ for (const event of events) {
610
+ const sessionId = typeof event.metadata?.sessionId === 'string' ? event.metadata.sessionId : null;
611
+ if (!sessionId)
612
+ continue;
613
+ const bucket = sessions.get(sessionId) ?? [];
614
+ bucket.push(event);
615
+ sessions.set(sessionId, bucket);
616
+ }
617
+ const out = [];
618
+ for (const [sessionId, sessionEvents] of sessions.entries()) {
619
+ const activityCount = sessionEvents.filter((event) => LEDGER_ACTIVITY_ACTIONS.has(event.actionType)).length;
620
+ const hasPersistence = sessionEvents.some((event) => LEDGER_PERSISTENCE_ACTIONS.has(event.actionType));
621
+ if (activityCount < 4 || hasPersistence) {
622
+ continue;
623
+ }
624
+ const missingWriteCategories = inferMissingWriteCategories(sessionEvents);
625
+ const host = typeof sessionEvents[0]?.metadata?.host === 'string'
626
+ ? String(sessionEvents[0].metadata?.host)
627
+ : (sessionEvents[0]?.source ?? null);
628
+ const summary = `Recent compliance lesson: ${host ?? 'this host'} left an under-logged run after substantial retrieval work without a checkpoint or durable write. This is non-compliant for Iranti; before the next pause, write ${formatWriteCategoryList(missingWriteCategories)} and checkpoint shared progress.`;
629
+ out.push({
630
+ actionType: 'checkpoint_discipline_lesson',
631
+ summary,
632
+ timestamp: latestTimestamp(sessionEvents),
633
+ source: sessionEvents[0]?.source ?? 'internal',
634
+ host: host || null,
635
+ sessionId,
636
+ entityKey: null,
637
+ reason: 'missing_checkpoint_after_activity',
638
+ category: 'compliance',
639
+ evidenceActionTypes: distinct(sessionEvents.map((event) => event.actionType)),
640
+ });
641
+ }
642
+ return out;
643
+ }
644
+ function describeHostCapabilities(events) {
645
+ const capabilities = distinct(events.flatMap((event) => {
646
+ switch (event.actionType) {
647
+ case 'memory_injected':
648
+ return ['memory injection'];
649
+ case 'query_executed':
650
+ return ['exact lookup'];
651
+ case 'search_executed':
652
+ return ['search'];
653
+ case 'related_executed':
654
+ case 'related_deep_executed':
655
+ return ['relationship lookup'];
656
+ case 'whoknows_executed':
657
+ return ['agent lookup'];
658
+ case 'checkpoint_written':
659
+ return ['shared checkpoints'];
660
+ case 'summary_written':
661
+ return ['strict summaries'];
662
+ case 'write_available':
663
+ case 'write_created':
664
+ case 'write_updated':
665
+ case 'write_replaced':
666
+ return ['durable writes'];
667
+ default:
668
+ return [];
669
+ }
670
+ }));
671
+ if (capabilities.length === 0)
672
+ return null;
673
+ if (capabilities.length === 1)
674
+ return capabilities[0];
675
+ if (capabilities.length === 2)
676
+ return `${capabilities[0]} and ${capabilities[1]}`;
677
+ return `${capabilities.slice(0, -1).join(', ')}, and ${capabilities[capabilities.length - 1]}`;
678
+ }
679
+ function summarizeFailureReasons(events) {
680
+ const reasons = distinct(events.map((event) => event.reason ?? 'host error').filter(Boolean));
681
+ return reasons.slice(0, 2).join('; ');
682
+ }
683
+ function buildHostLearnings(events) {
684
+ const byHost = new Map();
685
+ for (const event of events) {
686
+ const host = typeof event.metadata?.host === 'string' ? event.metadata.host : event.source;
687
+ const key = `${event.source}|${host}`;
688
+ const bucket = byHost.get(key) ?? [];
689
+ bucket.push(event);
690
+ byHost.set(key, bucket);
691
+ }
692
+ const out = [];
693
+ for (const [identity, hostEvents] of byHost.entries()) {
694
+ const [source, hostName] = identity.split('|');
695
+ const failures = hostEvents.filter((event) => (event.actionType === 'host_failure'
696
+ || event.actionType === 'integration_probe_failed'
697
+ || event.actionType === 'provider_fallback_used'));
698
+ const successes = hostEvents.filter((event) => !failures.includes(event));
699
+ if (failures.length === 0 && successes.length === 0)
700
+ continue;
701
+ const capabilitySummary = describeHostCapabilities(successes);
702
+ let summary = null;
703
+ if (failures.length > 0 && capabilitySummary) {
704
+ summary = `Recent host lesson: ${hostName} hit ${summarizeFailureReasons(failures)}, but ${source} still completed ${capabilitySummary}.`;
705
+ }
706
+ else if (failures.length > 0) {
707
+ summary = `Recent host lesson: ${hostName} hit ${summarizeFailureReasons(failures)}.`;
708
+ }
709
+ else if (capabilitySummary) {
710
+ summary = `Recent host lesson: ${hostName} successfully completed ${capabilitySummary}.`;
711
+ }
712
+ if (!summary)
713
+ continue;
714
+ out.push({
715
+ actionType: 'host_lesson',
716
+ summary,
717
+ timestamp: latestTimestamp(hostEvents),
718
+ source,
719
+ host: hostName || null,
720
+ sessionId: typeof hostEvents[0]?.metadata?.sessionId === 'string' ? String(hostEvents[0].metadata?.sessionId) : null,
721
+ entityKey: null,
722
+ reason: failures[0]?.reason ?? null,
723
+ category: 'host',
724
+ evidenceActionTypes: distinct(hostEvents.map((event) => event.actionType)),
725
+ });
726
+ }
727
+ return out;
728
+ }
729
+ function buildRecallLearnings(events) {
730
+ const recalls = events.filter((event) => event.actionType === 'mandatory_recall_forced');
731
+ const injections = events.filter((event) => event.actionType === 'memory_injected');
732
+ const out = [];
733
+ for (const recall of recalls) {
734
+ const entityKey = recall.entityType && recall.entityId && recall.key
735
+ ? `${recall.entityType}/${recall.entityId}/${recall.key}`
736
+ : null;
737
+ const matchingInjection = injections.find((event) => {
738
+ const injectedKeys = Array.isArray(event.metadata?.injectedKeys)
739
+ ? event.metadata.injectedKeys.map((value) => String(value))
740
+ : [];
741
+ return entityKey ? injectedKeys.includes(entityKey) : event.reason === recall.reason;
742
+ });
743
+ if (!matchingInjection)
744
+ continue;
745
+ out.push({
746
+ actionType: 'recall_lesson',
747
+ summary: entityKey
748
+ ? `Recent recall lesson: policy-forced recall successfully injected ${entityKey}.`
749
+ : 'Recent recall lesson: policy-forced recall successfully injected needed memory.',
750
+ timestamp: latestTimestamp([recall, matchingInjection]),
751
+ source: recall.source,
752
+ host: typeof recall.metadata?.host === 'string' ? recall.metadata.host : null,
753
+ sessionId: typeof recall.metadata?.sessionId === 'string' ? recall.metadata.sessionId : null,
754
+ entityKey,
755
+ reason: recall.reason ?? null,
756
+ category: 'recall',
757
+ evidenceActionTypes: ['mandatory_recall_forced', 'memory_injected'],
758
+ });
759
+ }
760
+ return out;
761
+ }
762
+ function buildPersistenceLearnings(events) {
763
+ const persistenceEvents = events.filter((event) => (event.actionType === 'checkpoint_written'
764
+ || event.actionType === 'summary_written'
765
+ || event.actionType === 'write_available'
766
+ || event.actionType === 'write_created'
767
+ || event.actionType === 'write_updated'
768
+ || event.actionType === 'write_replaced'));
769
+ const byEntity = new Map();
770
+ for (const event of persistenceEvents) {
771
+ const entityKey = event.entityType && event.entityId && event.key
772
+ ? `${event.entityType}/${event.entityId}/${event.key}`
773
+ : null;
774
+ if (!entityKey)
775
+ continue;
776
+ const bucket = byEntity.get(entityKey) ?? [];
777
+ bucket.push(event);
778
+ byEntity.set(entityKey, bucket);
779
+ }
780
+ const out = [];
781
+ for (const [entityKey, entityEvents] of byEntity.entries()) {
782
+ const hasCheckpoint = entityEvents.some((event) => event.actionType === 'checkpoint_written');
783
+ const hasDurable = entityEvents.some((event) => (event.actionType === 'summary_written'
784
+ || event.actionType === 'write_available'
785
+ || event.actionType === 'write_created'
786
+ || event.actionType === 'write_updated'
787
+ || event.actionType === 'write_replaced'));
788
+ if (!hasCheckpoint && !hasDurable)
789
+ continue;
790
+ let summary;
791
+ if (hasCheckpoint && hasDurable) {
792
+ summary = `Recent persistence lesson: ${entityKey} is being kept current through shared checkpoints and durable writes.`;
793
+ }
794
+ else if (hasCheckpoint) {
795
+ summary = `Recent persistence lesson: ${entityKey} is being handed off through shared checkpoints.`;
796
+ }
797
+ else {
798
+ summary = `Recent persistence lesson: ${entityKey} is being maintained through durable writes.`;
799
+ }
800
+ out.push({
801
+ actionType: 'persistence_lesson',
802
+ summary,
803
+ timestamp: latestTimestamp(entityEvents),
804
+ source: entityEvents[0]?.source ?? 'internal',
805
+ host: typeof entityEvents[0]?.metadata?.host === 'string' ? String(entityEvents[0].metadata?.host) : null,
806
+ sessionId: typeof entityEvents[0]?.metadata?.sessionId === 'string' ? String(entityEvents[0].metadata?.sessionId) : null,
807
+ entityKey,
808
+ reason: null,
809
+ category: 'persistence',
810
+ evidenceActionTypes: distinct(entityEvents.map((event) => event.actionType)),
811
+ });
812
+ }
813
+ return out;
814
+ }
815
+ async function summarizeSessionLedgerLearnings(input = {}) {
816
+ const events = await querySessionLedger({
817
+ agentId: input.agentId,
818
+ sessionId: input.sessionId,
819
+ source: input.source,
820
+ host: input.host,
821
+ since: input.since,
822
+ until: input.until,
823
+ limit: normalizeLimit(input.limit ?? 40),
824
+ });
825
+ const out = [];
826
+ const seen = new Set();
827
+ const synthesized = [
828
+ ...buildHostLearnings(events),
829
+ ...buildRecallLearnings(events),
830
+ ...buildPersistenceLearnings(events),
831
+ ...buildComplianceLearnings(events),
832
+ ].sort((left, right) => right.timestamp.localeCompare(left.timestamp));
833
+ for (const learning of synthesized) {
834
+ const identity = [
835
+ learning.actionType,
836
+ learning.entityKey ?? '',
837
+ learning.reason ?? '',
838
+ learning.summary,
839
+ ].join('|');
840
+ if (seen.has(identity))
841
+ continue;
842
+ seen.add(identity);
843
+ out.push(learning);
844
+ if (out.length >= normalizeLearningLimit(input.maxLearnings)) {
845
+ return out;
846
+ }
847
+ }
848
+ for (const event of events) {
849
+ const learning = eventToLearning(event);
850
+ if (!learning)
851
+ continue;
852
+ const identity = [
853
+ learning.actionType,
854
+ learning.entityKey ?? '',
855
+ learning.reason ?? '',
856
+ learning.summary,
857
+ ].join('|');
858
+ if (seen.has(identity))
859
+ continue;
860
+ seen.add(identity);
861
+ out.push(learning);
862
+ if (out.length >= normalizeLearningLimit(input.maxLearnings)) {
863
+ break;
864
+ }
865
+ }
866
+ return out;
867
+ }
868
+ async function summarizeMemoryAttribution(input = {}) {
869
+ const events = await querySessionLedger({
870
+ ...input,
871
+ actionTypes: ['memory_injected', 'memory_injection_scored'],
872
+ limit: normalizeLimit(input.limit ?? 200),
873
+ });
874
+ const byInjectionId = new Map();
875
+ for (const event of events.slice().sort((left, right) => left.timestamp.localeCompare(right.timestamp))) {
876
+ const injectionId = typeof event.metadata?.injectionId === 'string'
877
+ ? event.metadata.injectionId
878
+ : null;
879
+ if (!injectionId) {
880
+ continue;
881
+ }
882
+ const existing = byInjectionId.get(injectionId);
883
+ const injectedKeys = Array.isArray(event.metadata?.injectedKeys)
884
+ ? event.metadata.injectedKeys.map((value) => String(value)).filter(Boolean)
885
+ : existing?.injectedKeys ?? [];
886
+ const injectedEntryIds = Array.isArray(event.metadata?.injectedEntryIds)
887
+ ? event.metadata.injectedEntryIds
888
+ .map((value) => Number(value))
889
+ .filter((value) => Number.isInteger(value))
890
+ : existing?.injectedEntryIds ?? [];
891
+ const evidenceKinds = Array.isArray(event.metadata?.evidenceKinds)
892
+ ? event.metadata.evidenceKinds.map((value) => String(value)).filter(Boolean)
893
+ : existing?.evidenceKinds ?? [];
894
+ const sessionId = typeof event.metadata?.sessionId === 'string'
895
+ ? event.metadata.sessionId
896
+ : existing?.sessionId ?? null;
897
+ const host = typeof event.metadata?.host === 'string'
898
+ ? event.metadata.host
899
+ : existing?.host ?? null;
900
+ const phase = typeof event.metadata?.phase === 'string'
901
+ ? event.metadata.phase
902
+ : existing?.phase ?? null;
903
+ byInjectionId.set(injectionId, {
904
+ injectionId,
905
+ sessionId,
906
+ agentId: event.agentId,
907
+ source: event.source,
908
+ host,
909
+ surfacedAt: existing?.surfacedAt ?? event.timestamp,
910
+ scoredAt: event.actionType === 'memory_injection_scored'
911
+ ? event.timestamp
912
+ : (existing?.scoredAt ?? null),
913
+ surfaced: true,
914
+ used: event.actionType === 'memory_injection_scored'
915
+ ? event.metadata?.used === true
916
+ : (existing?.used ?? false),
917
+ helpful: event.actionType === 'memory_injection_scored'
918
+ ? event.metadata?.helpful === true
919
+ : (existing?.helpful ?? false),
920
+ injectedKeys,
921
+ injectedEntryIds,
922
+ evidenceKinds,
923
+ phase,
924
+ reason: event.reason ?? existing?.reason ?? null,
925
+ });
926
+ }
927
+ return Array.from(byInjectionId.values()).sort((left, right) => right.surfacedAt.localeCompare(left.surfacedAt));
928
+ }
929
+ async function buildSessionLedgerLearningProfile(input = {}) {
930
+ const events = await querySessionLedger({
931
+ agentId: input.agentId,
932
+ source: input.source,
933
+ since: input.since,
934
+ until: input.until,
935
+ limit: normalizeProfileLimit(input.limit),
936
+ });
937
+ if (events.length === 0) {
938
+ return null;
939
+ }
940
+ const hostEvents = input.host?.trim()
941
+ ? events.filter((event) => {
942
+ const host = typeof event.metadata?.host === 'string' ? event.metadata.host : null;
943
+ return host === input.host;
944
+ })
945
+ : [];
946
+ const taskScope = scopeEventsByTaskType(events, input.taskType);
947
+ const taskEvents = taskScope.events;
948
+ const globalEvents = events;
949
+ const scopes = [
950
+ { name: 'host', events: hostEvents },
951
+ { name: 'task', events: taskEvents },
952
+ { name: 'global', events: globalEvents },
953
+ ];
954
+ const scopesUsed = [];
955
+ const summaries = [];
956
+ const priorityKeys = [];
957
+ let preferMemoryForAmbiguousTurns = false;
958
+ let checkpointReminder = null;
959
+ let missingWriteCategories = [];
960
+ for (const scope of scopes) {
961
+ if (scope.events.length === 0)
962
+ continue;
963
+ scopesUsed.push(scope.name);
964
+ if (scopeSupportsAmbiguousMemory(scope.events)) {
965
+ preferMemoryForAmbiguousTurns = true;
966
+ }
967
+ if (!checkpointReminder) {
968
+ const complianceLearning = buildComplianceLearnings(scope.events)[0] ?? null;
969
+ checkpointReminder = complianceLearning?.summary ?? null;
970
+ missingWriteCategories = complianceLearning ? inferMissingWriteCategories(scope.events) : [];
971
+ }
972
+ for (const summary of scopeSummaries(scope.events)) {
973
+ if (!summaries.includes(summary)) {
974
+ summaries.push(summary);
975
+ }
976
+ }
977
+ for (const key of buildPriorityKeys(scope.events)) {
978
+ if (!priorityKeys.includes(key)) {
979
+ priorityKeys.push(key);
980
+ }
981
+ }
982
+ }
983
+ if (!preferMemoryForAmbiguousTurns && summaries.length === 0 && priorityKeys.length === 0) {
984
+ return null;
985
+ }
986
+ return {
987
+ scopesUsed,
988
+ preferMemoryForAmbiguousTurns,
989
+ priorityKeys: priorityKeys.slice(0, 6),
990
+ summaries: summaries.slice(0, 4),
991
+ matchedTaskType: taskScope.matchedTaskType,
992
+ needsCheckpointReminder: Boolean(checkpointReminder),
993
+ checkpointReminder,
994
+ missingWriteCategories,
995
+ };
996
+ }
997
+ //# sourceMappingURL=sessionLedger.js.map