blun-king-cli 9.1.389 → 9.1.391
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.
- package/bin/cognitive-memory-adapter.cjs +83 -4
- package/bin/cognitive-state-store.cjs +41 -8
- package/bin/cognitive-turn-lifecycle.cjs +8 -1
- package/bin/identity-context-policy.cjs +2 -0
- package/bin/personality-memory-adapter.cjs +6 -1
- package/bin/relationship-curiosity-policy.cjs +3 -1
- package/package.json +1 -1
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
|
|
4
4
|
|
|
5
|
-
const COGNITIVE_MEMORY_ADAPTER_VERSION =
|
|
5
|
+
const COGNITIVE_MEMORY_ADAPTER_VERSION = 6;
|
|
6
6
|
const KIND_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
7
|
-
const
|
|
7
|
+
const RETENTION_CLASSES = new Set(['operational', 'durable', 'relationship', 'audit']);
|
|
8
|
+
const ACCESS_REQUIRED_KEYS = Object.freeze([
|
|
8
9
|
'actorId', 'agentId', 'channelId', 'conversationId', 'permissionContext',
|
|
9
10
|
'portalId', 'requestedScope', 'tenantId', 'userId',
|
|
10
11
|
]);
|
|
11
|
-
const
|
|
12
|
+
const ACCESS_OPTIONAL_KEYS = Object.freeze(['identityLink']);
|
|
13
|
+
const PERMISSION_REQUIRED_KEYS = Object.freeze(['authority', 'decision', 'receiptId']);
|
|
14
|
+
const PERMISSION_OPTIONAL_KEYS = Object.freeze(['identityReceiptId']);
|
|
15
|
+
const IDENTITY_LINK_KEYS = Object.freeze([
|
|
16
|
+
'authority', 'canonicalUserId', 'confirmedAt', 'method', 'receiptId',
|
|
17
|
+
'sourcePortal', 'sourceSubjectId', 'status', 'tenantId', 'version',
|
|
18
|
+
]);
|
|
19
|
+
const IDENTITY_LINK_AUTHORITIES = new Map([
|
|
20
|
+
['verified_oauth', 'oauth_verifier'],
|
|
21
|
+
['secure_account_link', 'account_link_service'],
|
|
22
|
+
['explicit_confirmed', 'identity_confirmation_service'],
|
|
23
|
+
]);
|
|
12
24
|
const REQUIRED_METHODS = Object.freeze([
|
|
13
25
|
'commit',
|
|
14
26
|
'read',
|
|
@@ -30,13 +42,70 @@ function exactKeys(value, expected) {
|
|
|
30
42
|
&& Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
function requiredAndOptionalKeys(value, required, optional) {
|
|
46
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
47
|
+
const allowed = new Set([...required, ...optional]);
|
|
48
|
+
return required.every((key) => Object.prototype.hasOwnProperty.call(value, key))
|
|
49
|
+
&& Object.keys(value).every((key) => allowed.has(key));
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
function safeId(value) {
|
|
34
53
|
const text = String(value ?? '').trim();
|
|
35
54
|
return KIND_RE.test(text) ? text : '';
|
|
36
55
|
}
|
|
37
56
|
|
|
57
|
+
function normalizeIdentityLink(access, normalized) {
|
|
58
|
+
const link = access.identityLink;
|
|
59
|
+
const identityReceiptId = normalized.permissionContext.identityReceiptId;
|
|
60
|
+
if (normalized.userId === normalized.actorId) {
|
|
61
|
+
if (link !== undefined || identityReceiptId !== undefined) fail('COGNITIVE_IDENTITY_LINK_INVALID');
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
if (link === undefined || link === null) fail('COGNITIVE_IDENTITY_LINK_REQUIRED');
|
|
65
|
+
if (!exactKeys(link, IDENTITY_LINK_KEYS)) fail('COGNITIVE_IDENTITY_LINK_INVALID');
|
|
66
|
+
if (String(link.status ?? '') === 'revoked') fail('COGNITIVE_IDENTITY_LINK_REVOKED');
|
|
67
|
+
const method = String(link.method ?? '');
|
|
68
|
+
const normalizedLink = {
|
|
69
|
+
version: Number(link.version),
|
|
70
|
+
tenantId: safeId(link.tenantId),
|
|
71
|
+
canonicalUserId: safeId(link.canonicalUserId),
|
|
72
|
+
sourcePortal: safeId(link.sourcePortal),
|
|
73
|
+
sourceSubjectId: safeId(link.sourceSubjectId),
|
|
74
|
+
method,
|
|
75
|
+
status: String(link.status ?? ''),
|
|
76
|
+
confirmedAt: String(link.confirmedAt ?? '').trim(),
|
|
77
|
+
authority: safeId(link.authority),
|
|
78
|
+
receiptId: safeId(link.receiptId),
|
|
79
|
+
};
|
|
80
|
+
if (normalizedLink.version !== 1 || normalizedLink.status !== 'active'
|
|
81
|
+
|| Number.isNaN(Date.parse(normalizedLink.confirmedAt))
|
|
82
|
+
|| IDENTITY_LINK_AUTHORITIES.get(method) !== normalizedLink.authority
|
|
83
|
+
|| normalizedLink.tenantId !== normalized.tenantId
|
|
84
|
+
|| normalizedLink.canonicalUserId !== normalized.userId
|
|
85
|
+
|| normalizedLink.sourcePortal !== normalized.portalId
|
|
86
|
+
|| normalizedLink.sourceSubjectId !== normalized.actorId
|
|
87
|
+
|| !identityReceiptId || normalizedLink.receiptId !== identityReceiptId) {
|
|
88
|
+
fail('COGNITIVE_IDENTITY_LINK_INVALID');
|
|
89
|
+
}
|
|
90
|
+
const canonicalScope = `relationship:${normalized.userId}:private`;
|
|
91
|
+
const personalScope = `user:${normalized.userId}:private`;
|
|
92
|
+
if ((normalized.requestedScope.startsWith('relationship:') && normalized.requestedScope !== canonicalScope)
|
|
93
|
+
|| (normalized.requestedScope.startsWith('user:') && normalized.requestedScope !== personalScope)) {
|
|
94
|
+
fail('COGNITIVE_IDENTITY_SCOPE_MISMATCH');
|
|
95
|
+
}
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
...normalizedLink,
|
|
98
|
+
confirmedAt: new Date(normalizedLink.confirmedAt).toISOString(),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
38
102
|
function normalizeMemoryAccess(access, request) {
|
|
39
|
-
if (!
|
|
103
|
+
if (!requiredAndOptionalKeys(access, ACCESS_REQUIRED_KEYS, ACCESS_OPTIONAL_KEYS)
|
|
104
|
+
|| !requiredAndOptionalKeys(
|
|
105
|
+
access.permissionContext,
|
|
106
|
+
PERMISSION_REQUIRED_KEYS,
|
|
107
|
+
PERMISSION_OPTIONAL_KEYS,
|
|
108
|
+
)) {
|
|
40
109
|
fail('COGNITIVE_MEMORY_ACCESS_INVALID');
|
|
41
110
|
}
|
|
42
111
|
const normalized = {
|
|
@@ -52,6 +121,9 @@ function normalizeMemoryAccess(access, request) {
|
|
|
52
121
|
authority: safeId(access.permissionContext.authority),
|
|
53
122
|
decision: String(access.permissionContext.decision ?? ''),
|
|
54
123
|
receiptId: safeId(access.permissionContext.receiptId),
|
|
124
|
+
...(Object.prototype.hasOwnProperty.call(access.permissionContext, 'identityReceiptId')
|
|
125
|
+
? { identityReceiptId: safeId(access.permissionContext.identityReceiptId) }
|
|
126
|
+
: {}),
|
|
55
127
|
},
|
|
56
128
|
};
|
|
57
129
|
if (!normalized.tenantId || !normalized.userId || !normalized.actorId || !normalized.agentId
|
|
@@ -65,6 +137,8 @@ function normalizeMemoryAccess(access, request) {
|
|
|
65
137
|
|| requestTenant !== normalized.tenantId || requestAgent !== normalized.agentId) {
|
|
66
138
|
fail('COGNITIVE_MEMORY_ACCESS_SCOPE_MISMATCH');
|
|
67
139
|
}
|
|
140
|
+
const identityLink = normalizeIdentityLink(access, normalized);
|
|
141
|
+
if (identityLink) normalized.identityLink = identityLink;
|
|
68
142
|
normalized.permissionContext = Object.freeze(normalized.permissionContext);
|
|
69
143
|
return Object.freeze(normalized);
|
|
70
144
|
}
|
|
@@ -86,6 +160,11 @@ function assertVisibleScope(scope, access) {
|
|
|
86
160
|
}
|
|
87
161
|
|
|
88
162
|
function assertCommitAccess(input, access) {
|
|
163
|
+
if (Number.isNaN(Date.parse(String(input?.occurredAt ?? '')))
|
|
164
|
+
|| Number.isNaN(Date.parse(String(input?.receivedAt ?? '')))
|
|
165
|
+
|| !RETENTION_CLASSES.has(String(input?.retentionClass ?? ''))) {
|
|
166
|
+
fail('COGNITIVE_MEMORY_EVENT_ENVELOPE_INVALID');
|
|
167
|
+
}
|
|
89
168
|
if (isInternalAgentAccess(access)) return;
|
|
90
169
|
if (!Array.isArray(input?.observations) || input.observations.length < 1) {
|
|
91
170
|
fail('COGNITIVE_MEMORY_VISIBILITY_DENIED');
|
|
@@ -10,6 +10,7 @@ const DOMAINS = new Set(['self', 'world', 'team', 'goal', 'open_thread', 'assump
|
|
|
10
10
|
const EPISTEMIC_STATES = new Set([
|
|
11
11
|
'verified', 'credible_unverified', 'hypothesis', 'uncertain_memory', 'stale', 'unknown', 'legacy_unknown',
|
|
12
12
|
]);
|
|
13
|
+
const RETENTION_CLASSES = new Set(['operational', 'durable', 'relationship', 'audit']);
|
|
13
14
|
const FORBIDDEN_KEY_RE = /(?:^|_)(?:acl|api_key|capability|password|permission|secret|token)(?:_|$)/iu;
|
|
14
15
|
const MAX_EVENT_BYTES = 64 * 1024;
|
|
15
16
|
const MAX_OBSERVATIONS = 32;
|
|
@@ -90,13 +91,21 @@ function normalizeObservation(value) {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
function normalizeEvent(input) {
|
|
94
|
+
const allowed = new Set([
|
|
95
|
+
'tenantId', 'agentId', 'eventId', 'expectedVersion', 'occurredAt', 'receivedAt',
|
|
96
|
+
'retentionClass', 'source', 'observations',
|
|
97
|
+
]);
|
|
98
|
+
if (!exactKeys(input, allowed) || hasForbiddenKey(input)) fail('COGNITIVE_FORBIDDEN_FIELD');
|
|
93
99
|
const tenantId = safeId(input.tenantId);
|
|
94
100
|
const agentId = safeId(input.agentId);
|
|
95
101
|
const eventId = safeId(input.eventId);
|
|
96
102
|
const expectedVersion = Number(input.expectedVersion);
|
|
97
103
|
const occurredAt = String(input.occurredAt ?? '').trim();
|
|
104
|
+
const receivedAt = String(input.receivedAt ?? '').trim();
|
|
105
|
+
const retentionClass = String(input.retentionClass ?? '').trim();
|
|
98
106
|
if (!tenantId || !agentId || !eventId || !Number.isSafeInteger(expectedVersion)
|
|
99
107
|
|| expectedVersion < 0 || Number.isNaN(Date.parse(occurredAt))
|
|
108
|
+
|| Number.isNaN(Date.parse(receivedAt)) || !RETENTION_CLASSES.has(retentionClass)
|
|
100
109
|
|| !Array.isArray(input.observations) || input.observations.length < 1
|
|
101
110
|
|| input.observations.length > MAX_OBSERVATIONS) fail('COGNITIVE_INVALID_EVENT');
|
|
102
111
|
const normalized = {
|
|
@@ -104,7 +113,9 @@ function normalizeEvent(input) {
|
|
|
104
113
|
agent_id: agentId,
|
|
105
114
|
event_id: eventId,
|
|
106
115
|
expected_version: expectedVersion,
|
|
107
|
-
occurred_at: occurredAt,
|
|
116
|
+
occurred_at: new Date(occurredAt).toISOString(),
|
|
117
|
+
received_at: new Date(receivedAt).toISOString(),
|
|
118
|
+
retention_class: retentionClass,
|
|
108
119
|
source: normalizeSource(input.source),
|
|
109
120
|
observations: input.observations.map(normalizeObservation),
|
|
110
121
|
};
|
|
@@ -146,6 +157,8 @@ function initialize(db) {
|
|
|
146
157
|
expected_version INTEGER NOT NULL,
|
|
147
158
|
new_version INTEGER NOT NULL,
|
|
148
159
|
occurred_at TEXT NOT NULL,
|
|
160
|
+
received_at TEXT NOT NULL,
|
|
161
|
+
retention_class TEXT NOT NULL,
|
|
149
162
|
payload_json TEXT NOT NULL,
|
|
150
163
|
previous_hash TEXT NOT NULL,
|
|
151
164
|
event_hash TEXT NOT NULL UNIQUE
|
|
@@ -163,6 +176,8 @@ function initialize(db) {
|
|
|
163
176
|
scope TEXT NOT NULL,
|
|
164
177
|
source_json TEXT NOT NULL,
|
|
165
178
|
occurred_at TEXT NOT NULL,
|
|
179
|
+
received_at TEXT NOT NULL,
|
|
180
|
+
retention_class TEXT NOT NULL,
|
|
166
181
|
supersedes_observation_id TEXT,
|
|
167
182
|
withdraws_observation_id TEXT
|
|
168
183
|
);
|
|
@@ -200,6 +215,19 @@ function initialize(db) {
|
|
|
200
215
|
if (!observationColumns.some((column) => column.name === 'epistemic_state')) {
|
|
201
216
|
db.exec("ALTER TABLE cognitive_observations ADD COLUMN epistemic_state TEXT NOT NULL DEFAULT 'legacy_unknown'");
|
|
202
217
|
}
|
|
218
|
+
if (!observationColumns.some((column) => column.name === 'received_at')) {
|
|
219
|
+
db.exec("ALTER TABLE cognitive_observations ADD COLUMN received_at TEXT NOT NULL DEFAULT ''");
|
|
220
|
+
}
|
|
221
|
+
if (!observationColumns.some((column) => column.name === 'retention_class')) {
|
|
222
|
+
db.exec("ALTER TABLE cognitive_observations ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'legacy_unknown'");
|
|
223
|
+
}
|
|
224
|
+
const eventColumns = db.prepare('PRAGMA table_info(cognitive_events)').all();
|
|
225
|
+
if (!eventColumns.some((column) => column.name === 'received_at')) {
|
|
226
|
+
db.exec("ALTER TABLE cognitive_events ADD COLUMN received_at TEXT NOT NULL DEFAULT ''");
|
|
227
|
+
}
|
|
228
|
+
if (!eventColumns.some((column) => column.name === 'retention_class')) {
|
|
229
|
+
db.exec("ALTER TABLE cognitive_events ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'legacy_unknown'");
|
|
230
|
+
}
|
|
203
231
|
}
|
|
204
232
|
|
|
205
233
|
function openCognitiveStateStore({ home } = {}) {
|
|
@@ -227,12 +255,13 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
227
255
|
const eventHash = crypto.createHash('sha256').update(`${previousHash}\0${payload}`).digest('hex');
|
|
228
256
|
const newVersion = actualVersion + 1;
|
|
229
257
|
db.prepare(`INSERT INTO cognitive_events
|
|
230
|
-
(event_id, tenant_id, agent_id, expected_version, new_version, occurred_at, payload_json, previous_hash, event_hash)
|
|
231
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
232
|
-
.run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion,
|
|
258
|
+
(event_id, tenant_id, agent_id, expected_version, new_version, occurred_at, received_at, retention_class, payload_json, previous_hash, event_hash)
|
|
259
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
260
|
+
.run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion,
|
|
261
|
+
normalized.occurred_at, normalized.received_at, normalized.retention_class, payload, previousHash, eventHash);
|
|
233
262
|
const insertObservation = db.prepare(`INSERT INTO cognitive_observations
|
|
234
|
-
(observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, supersedes_observation_id, withdraws_observation_id)
|
|
235
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
263
|
+
(observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, received_at, retention_class, supersedes_observation_id, withdraws_observation_id)
|
|
264
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
236
265
|
const sourceJson = JSON.stringify(normalized.source);
|
|
237
266
|
for (const observation of normalized.observations) {
|
|
238
267
|
if (observation.supersedes) {
|
|
@@ -252,7 +281,8 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
252
281
|
insertObservation.run(observation.observation_id, normalized.event_id, normalized.tenant_id, normalized.agent_id,
|
|
253
282
|
observation.domain, observation.key, observation.value, observation.confidence, observation.epistemic_state,
|
|
254
283
|
observation.scope, sourceJson,
|
|
255
|
-
normalized.occurred_at,
|
|
284
|
+
normalized.occurred_at, normalized.received_at, normalized.retention_class,
|
|
285
|
+
observation.supersedes ?? null, observation.withdraws ?? null);
|
|
256
286
|
}
|
|
257
287
|
db.prepare(`INSERT INTO cognitive_streams (tenant_id, agent_id, version, updated_at) VALUES (?, ?, ?, ?)
|
|
258
288
|
ON CONFLICT(tenant_id, agent_id) DO UPDATE SET version = excluded.version, updated_at = excluded.updated_at`)
|
|
@@ -273,7 +303,8 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
273
303
|
fail('COGNITIVE_INVALID_EVENT');
|
|
274
304
|
}
|
|
275
305
|
const stream = db.prepare('SELECT version, updated_at FROM cognitive_streams WHERE tenant_id = ? AND agent_id = ?').get(tenant, agent);
|
|
276
|
-
const select = `SELECT domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at,
|
|
306
|
+
const select = `SELECT domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, received_at,
|
|
307
|
+
retention_class, observation_id,
|
|
277
308
|
supersedes_observation_id, withdraws_observation_id FROM cognitive_observations`;
|
|
278
309
|
const rows = requestedVisibility && requestedVisibility !== 'agent:memory'
|
|
279
310
|
? db.prepare(`${select} WHERE tenant_id = ? AND agent_id = ? AND scope = ? ORDER BY occurred_at, rowid`)
|
|
@@ -292,6 +323,8 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
292
323
|
scope: row.scope,
|
|
293
324
|
source: JSON.parse(row.source_json),
|
|
294
325
|
occurred_at: row.occurred_at,
|
|
326
|
+
received_at: row.received_at || row.occurred_at,
|
|
327
|
+
retention_class: row.retention_class || 'legacy_unknown',
|
|
295
328
|
supersedes: row.supersedes_observation_id ?? null,
|
|
296
329
|
withdraws: row.withdraws_observation_id ?? null,
|
|
297
330
|
})),
|
|
@@ -127,6 +127,8 @@ function createCognitiveTurnLifecycle({
|
|
|
127
127
|
eventId,
|
|
128
128
|
expectedVersion,
|
|
129
129
|
occurredAt,
|
|
130
|
+
receivedAt: occurredAt,
|
|
131
|
+
retentionClass: 'operational',
|
|
130
132
|
source: {
|
|
131
133
|
provider: 'runtime',
|
|
132
134
|
channel_id: 'runtime',
|
|
@@ -176,6 +178,8 @@ function createCognitiveTurnLifecycle({
|
|
|
176
178
|
eventId,
|
|
177
179
|
expectedVersion,
|
|
178
180
|
occurredAt,
|
|
181
|
+
receivedAt: occurredAt,
|
|
182
|
+
retentionClass: 'durable',
|
|
179
183
|
source: {
|
|
180
184
|
provider: 'runtime',
|
|
181
185
|
channel_id: 'runtime',
|
|
@@ -310,6 +314,7 @@ function createCognitiveTurnLifecycle({
|
|
|
310
314
|
...(correction ? { supersedes: targetObservationId } : { withdraws: targetObservationId }),
|
|
311
315
|
};
|
|
312
316
|
const normalizedOccurredAt = new Date(occurredAt).toISOString();
|
|
317
|
+
const receivedAt = stageTime(`revision:${kind}:${requestId}`);
|
|
313
318
|
const revisionMemoryAccess = {
|
|
314
319
|
tenantId: tenant,
|
|
315
320
|
userId: normalizedSource.actor_id,
|
|
@@ -353,6 +358,8 @@ function createCognitiveTurnLifecycle({
|
|
|
353
358
|
eventId,
|
|
354
359
|
expectedVersion: current.version,
|
|
355
360
|
occurredAt: normalizedOccurredAt,
|
|
361
|
+
receivedAt,
|
|
362
|
+
retentionClass: 'durable',
|
|
356
363
|
source: normalizedSource,
|
|
357
364
|
observations: [observation],
|
|
358
365
|
}, revisionMemoryAccess);
|
|
@@ -466,7 +473,7 @@ function createCognitiveTurnLifecycle({
|
|
|
466
473
|
const authorization = input?.authorization;
|
|
467
474
|
const memoryAccess = {
|
|
468
475
|
tenantId: tenant,
|
|
469
|
-
userId:
|
|
476
|
+
userId: agent,
|
|
470
477
|
actorId: agent,
|
|
471
478
|
agentId: agent,
|
|
472
479
|
portalId: 'cli',
|
|
@@ -462,6 +462,7 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
462
462
|
const groupId = chatId.startsWith('-') ? `telegram-chat-${chatId.slice(1)}` : undefined;
|
|
463
463
|
const displayName = cleanText(meta.user, 120) || actorId;
|
|
464
464
|
const firstSeenAt = trustedTimestamp(meta.ts);
|
|
465
|
+
const receivedAt = trustedTimestamp(meta.received_at) || firstSeenAt;
|
|
465
466
|
const created = [];
|
|
466
467
|
if (createJsonOnce(root, ['actors', `${actorId}.json`], {
|
|
467
468
|
version: 1,
|
|
@@ -517,6 +518,7 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
517
518
|
sourceChannel: groupId ? 'group' : 'direct',
|
|
518
519
|
conversationId: groupId || `telegram-dm-${chatId}`,
|
|
519
520
|
messageId: safeId(meta.message_id),
|
|
521
|
+
receivedAt,
|
|
520
522
|
});
|
|
521
523
|
if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
522
524
|
const modelContext = renderIdentityContext({
|
|
@@ -9,7 +9,7 @@ const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
|
9
9
|
const SENSITIVITIES = new Set(['low', 'medium']);
|
|
10
10
|
const INPUT_KEYS = [
|
|
11
11
|
'actorId', 'agentId', 'conversationId', 'cooldownMs', 'messageId', 'occurredAt',
|
|
12
|
-
'sensitivity', 'sourceChannel', 'sourcePortal', 'tenantId', 'topic',
|
|
12
|
+
'receivedAt', 'sensitivity', 'sourceChannel', 'sourcePortal', 'tenantId', 'topic',
|
|
13
13
|
];
|
|
14
14
|
const MAX_COOLDOWN_MS = 90 * 24 * 60 * 60 * 1000;
|
|
15
15
|
|
|
@@ -49,6 +49,7 @@ function normalizePresentation(input) {
|
|
|
49
49
|
conversationId: safeId(input.conversationId),
|
|
50
50
|
messageId: safeId(input.messageId),
|
|
51
51
|
occurredAt: String(input.occurredAt ?? '').trim(),
|
|
52
|
+
receivedAt: String(input.receivedAt ?? '').trim(),
|
|
52
53
|
topic: safeId(input.topic),
|
|
53
54
|
sensitivity: String(input.sensitivity ?? ''),
|
|
54
55
|
cooldownMs: Number(input.cooldownMs),
|
|
@@ -57,11 +58,13 @@ function normalizePresentation(input) {
|
|
|
57
58
|
|| !normalized.sourcePortal || !normalized.sourceChannel || !normalized.conversationId
|
|
58
59
|
|| !normalized.messageId || !normalized.topic || !SENSITIVITIES.has(normalized.sensitivity)
|
|
59
60
|
|| Number.isNaN(Date.parse(normalized.occurredAt))
|
|
61
|
+
|| Number.isNaN(Date.parse(normalized.receivedAt))
|
|
60
62
|
|| !Number.isSafeInteger(normalized.cooldownMs) || normalized.cooldownMs < 1000
|
|
61
63
|
|| normalized.cooldownMs > MAX_COOLDOWN_MS) {
|
|
62
64
|
fail('PERSONALITY_MEMORY_INVALID_INPUT');
|
|
63
65
|
}
|
|
64
66
|
normalized.occurredAt = new Date(normalized.occurredAt).toISOString();
|
|
67
|
+
normalized.receivedAt = new Date(normalized.receivedAt).toISOString();
|
|
65
68
|
return normalized;
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -138,6 +141,8 @@ function createPersonalityMemoryAdapter({ identityRoot, memoryAdapter } = {}) {
|
|
|
138
141
|
eventId,
|
|
139
142
|
expectedVersion: current.version,
|
|
140
143
|
occurredAt: value.occurredAt,
|
|
144
|
+
receivedAt: value.receivedAt,
|
|
145
|
+
retentionClass: 'relationship',
|
|
141
146
|
source: {
|
|
142
147
|
provider: value.sourcePortal,
|
|
143
148
|
channel_id: value.sourceChannel,
|
|
@@ -64,11 +64,12 @@ function prepareCuriosityOpportunity(input = {}) {
|
|
|
64
64
|
const agentId = safeId(input.agentId);
|
|
65
65
|
const actorId = safeId(input.actorId);
|
|
66
66
|
const occurredAt = trustedTimestamp(input.occurredAt);
|
|
67
|
+
const receivedAt = trustedTimestamp(input.receivedAt);
|
|
67
68
|
const sourcePortal = safeId(input.sourcePortal) || 'telegram';
|
|
68
69
|
const sourceChannel = safeId(input.sourceChannel) || 'direct';
|
|
69
70
|
const conversationId = safeId(input.conversationId) || digestId('conversation', [actorId]);
|
|
70
71
|
const cue = curiosityCue(input.text);
|
|
71
|
-
if (!tenantId || !agentId || !actorId || !occurredAt || !cue) return undefined;
|
|
72
|
+
if (!tenantId || !agentId || !actorId || !occurredAt || !receivedAt || !cue) return undefined;
|
|
72
73
|
const messageId = safeId(input.messageId) || digestId('message', [
|
|
73
74
|
sourcePortal, sourceChannel, conversationId, actorId, occurredAt, normalizedText(input.text),
|
|
74
75
|
]);
|
|
@@ -88,6 +89,7 @@ function prepareCuriosityOpportunity(input = {}) {
|
|
|
88
89
|
conversationId,
|
|
89
90
|
messageId,
|
|
90
91
|
occurredAt,
|
|
92
|
+
receivedAt,
|
|
91
93
|
topic: cue.topic,
|
|
92
94
|
sensitivity: cue.sensitivity,
|
|
93
95
|
cooldownMs: COOLDOWN_MS,
|