blun-king-cli 9.1.303 → 9.1.305
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-state-store.cjs +249 -0
- package/bin/cognitive-turn-lifecycle.cjs +158 -0
- package/blun.mjs +45 -1
- package/package.json +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
7
|
+
|
|
8
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
9
|
+
const DOMAINS = new Set(['self', 'world', 'team', 'goal', 'open_thread', 'assumption', 'next_trigger', 'expected_evidence']);
|
|
10
|
+
const FORBIDDEN_KEY_RE = /(?:^|_)(?:acl|api_key|capability|password|permission|secret|token)(?:_|$)/iu;
|
|
11
|
+
const MAX_EVENT_BYTES = 64 * 1024;
|
|
12
|
+
const MAX_OBSERVATIONS = 32;
|
|
13
|
+
|
|
14
|
+
function fail(code) {
|
|
15
|
+
const error = new Error(code);
|
|
16
|
+
error.code = code;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function safeId(value) {
|
|
21
|
+
const text = String(value ?? '').trim();
|
|
22
|
+
return SAFE_ID_RE.test(text) ? text : '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanText(value, max = 512) {
|
|
26
|
+
const text = String(value ?? '').replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim();
|
|
27
|
+
return text && text.length <= max ? text : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasForbiddenKey(value) {
|
|
31
|
+
if (!value || typeof value !== 'object') return false;
|
|
32
|
+
if (Array.isArray(value)) return value.some(hasForbiddenKey);
|
|
33
|
+
return Object.entries(value).some(([key, child]) => FORBIDDEN_KEY_RE.test(key) || hasForbiddenKey(child));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function exactKeys(value, keys) {
|
|
37
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
38
|
+
&& Object.keys(value).every((key) => keys.has(key));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeSource(source) {
|
|
42
|
+
const allowed = new Set(['provider', 'actor_id', 'context_id', 'message_id']);
|
|
43
|
+
if (!exactKeys(source, allowed) || hasForbiddenKey(source)) fail('COGNITIVE_FORBIDDEN_FIELD');
|
|
44
|
+
const normalized = {
|
|
45
|
+
provider: safeId(source.provider),
|
|
46
|
+
actor_id: safeId(source.actor_id),
|
|
47
|
+
context_id: safeId(source.context_id),
|
|
48
|
+
message_id: safeId(source.message_id),
|
|
49
|
+
};
|
|
50
|
+
if (Object.values(normalized).some((value) => !value)) fail('COGNITIVE_INVALID_EVENT');
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeObservation(value) {
|
|
55
|
+
const allowed = new Set(['observation_id', 'domain', 'key', 'value', 'confidence', 'scope']);
|
|
56
|
+
if (!exactKeys(value, allowed) || hasForbiddenKey(value)) fail('COGNITIVE_FORBIDDEN_FIELD');
|
|
57
|
+
const observation = {
|
|
58
|
+
observation_id: safeId(value.observation_id),
|
|
59
|
+
domain: String(value.domain ?? ''),
|
|
60
|
+
key: cleanText(value.key, 128),
|
|
61
|
+
value: cleanText(value.value, 2048),
|
|
62
|
+
confidence: Number(value.confidence),
|
|
63
|
+
scope: cleanText(value.scope, 128),
|
|
64
|
+
};
|
|
65
|
+
if (!observation.observation_id || !DOMAINS.has(observation.domain) || !observation.key
|
|
66
|
+
|| !observation.value || !Number.isFinite(observation.confidence)
|
|
67
|
+
|| observation.confidence < 0 || observation.confidence > 1 || !observation.scope) {
|
|
68
|
+
fail('COGNITIVE_INVALID_EVENT');
|
|
69
|
+
}
|
|
70
|
+
if (FORBIDDEN_KEY_RE.test(observation.key)) fail('COGNITIVE_FORBIDDEN_FIELD');
|
|
71
|
+
return observation;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeEvent(input) {
|
|
75
|
+
const tenantId = safeId(input.tenantId);
|
|
76
|
+
const agentId = safeId(input.agentId);
|
|
77
|
+
const eventId = safeId(input.eventId);
|
|
78
|
+
const expectedVersion = Number(input.expectedVersion);
|
|
79
|
+
const occurredAt = String(input.occurredAt ?? '').trim();
|
|
80
|
+
if (!tenantId || !agentId || !eventId || !Number.isSafeInteger(expectedVersion)
|
|
81
|
+
|| expectedVersion < 0 || Number.isNaN(Date.parse(occurredAt))
|
|
82
|
+
|| !Array.isArray(input.observations) || input.observations.length < 1
|
|
83
|
+
|| input.observations.length > MAX_OBSERVATIONS) fail('COGNITIVE_INVALID_EVENT');
|
|
84
|
+
const normalized = {
|
|
85
|
+
tenant_id: tenantId,
|
|
86
|
+
agent_id: agentId,
|
|
87
|
+
event_id: eventId,
|
|
88
|
+
expected_version: expectedVersion,
|
|
89
|
+
occurred_at: occurredAt,
|
|
90
|
+
source: normalizeSource(input.source),
|
|
91
|
+
observations: input.observations.map(normalizeObservation),
|
|
92
|
+
};
|
|
93
|
+
const ids = new Set(normalized.observations.map((item) => item.observation_id));
|
|
94
|
+
if (ids.size !== normalized.observations.length) fail('COGNITIVE_INVALID_EVENT');
|
|
95
|
+
const payload = JSON.stringify(normalized);
|
|
96
|
+
if (Buffer.byteLength(payload) > MAX_EVENT_BYTES) fail('COGNITIVE_INVALID_EVENT');
|
|
97
|
+
return { normalized, payload };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function ensureHome(home) {
|
|
101
|
+
const root = path.resolve(String(home ?? ''));
|
|
102
|
+
try {
|
|
103
|
+
const stat = fs.lstatSync(root);
|
|
104
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('COGNITIVE_UNSAFE_HOME');
|
|
105
|
+
return fs.realpathSync(root);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error?.code === 'COGNITIVE_UNSAFE_HOME') throw error;
|
|
108
|
+
fail('COGNITIVE_UNSAFE_HOME');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function initialize(db) {
|
|
113
|
+
db.exec(`
|
|
114
|
+
PRAGMA foreign_keys = ON;
|
|
115
|
+
PRAGMA journal_mode = WAL;
|
|
116
|
+
PRAGMA busy_timeout = 5000;
|
|
117
|
+
CREATE TABLE IF NOT EXISTS cognitive_streams (
|
|
118
|
+
tenant_id TEXT NOT NULL,
|
|
119
|
+
agent_id TEXT NOT NULL,
|
|
120
|
+
version INTEGER NOT NULL,
|
|
121
|
+
updated_at TEXT NOT NULL,
|
|
122
|
+
PRIMARY KEY (tenant_id, agent_id)
|
|
123
|
+
);
|
|
124
|
+
CREATE TABLE IF NOT EXISTS cognitive_events (
|
|
125
|
+
event_id TEXT PRIMARY KEY,
|
|
126
|
+
tenant_id TEXT NOT NULL,
|
|
127
|
+
agent_id TEXT NOT NULL,
|
|
128
|
+
expected_version INTEGER NOT NULL,
|
|
129
|
+
new_version INTEGER NOT NULL,
|
|
130
|
+
occurred_at TEXT NOT NULL,
|
|
131
|
+
payload_json TEXT NOT NULL,
|
|
132
|
+
previous_hash TEXT NOT NULL,
|
|
133
|
+
event_hash TEXT NOT NULL UNIQUE
|
|
134
|
+
);
|
|
135
|
+
CREATE TABLE IF NOT EXISTS cognitive_observations (
|
|
136
|
+
observation_id TEXT PRIMARY KEY,
|
|
137
|
+
event_id TEXT NOT NULL REFERENCES cognitive_events(event_id),
|
|
138
|
+
tenant_id TEXT NOT NULL,
|
|
139
|
+
agent_id TEXT NOT NULL,
|
|
140
|
+
domain TEXT NOT NULL,
|
|
141
|
+
fact_key TEXT NOT NULL,
|
|
142
|
+
value_text TEXT NOT NULL,
|
|
143
|
+
confidence REAL NOT NULL,
|
|
144
|
+
scope TEXT NOT NULL,
|
|
145
|
+
source_json TEXT NOT NULL,
|
|
146
|
+
occurred_at TEXT NOT NULL
|
|
147
|
+
);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS cognitive_observations_stream
|
|
149
|
+
ON cognitive_observations (tenant_id, agent_id, occurred_at, observation_id);
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function openCognitiveStateStore({ home } = {}) {
|
|
154
|
+
const root = ensureHome(home);
|
|
155
|
+
const stateRoot = path.join(root, 'state');
|
|
156
|
+
fs.mkdirSync(stateRoot, { recursive: true });
|
|
157
|
+
if (!fs.realpathSync(stateRoot).startsWith(`${root}${path.sep}`)) fail('COGNITIVE_UNSAFE_HOME');
|
|
158
|
+
const db = new DatabaseSync(path.join(stateRoot, 'cognitive-state.sqlite'));
|
|
159
|
+
initialize(db);
|
|
160
|
+
|
|
161
|
+
function commit(input) {
|
|
162
|
+
const { normalized, payload } = normalizeEvent(input);
|
|
163
|
+
const existing = db.prepare('SELECT payload_json, new_version, event_hash FROM cognitive_events WHERE event_id = ?').get(normalized.event_id);
|
|
164
|
+
if (existing) {
|
|
165
|
+
if (existing.payload_json !== payload) fail('COGNITIVE_EVENT_ID_REUSE');
|
|
166
|
+
return { version: existing.new_version, event_hash: existing.event_hash, idempotent: true };
|
|
167
|
+
}
|
|
168
|
+
db.exec('BEGIN IMMEDIATE');
|
|
169
|
+
try {
|
|
170
|
+
const stream = db.prepare('SELECT version FROM cognitive_streams WHERE tenant_id = ? AND agent_id = ?').get(normalized.tenant_id, normalized.agent_id);
|
|
171
|
+
const actualVersion = Number(stream?.version ?? 0);
|
|
172
|
+
if (actualVersion !== normalized.expected_version) fail('COGNITIVE_VERSION_CONFLICT');
|
|
173
|
+
const previous = db.prepare('SELECT event_hash FROM cognitive_events WHERE tenant_id = ? AND agent_id = ? ORDER BY new_version DESC LIMIT 1').get(normalized.tenant_id, normalized.agent_id);
|
|
174
|
+
const previousHash = String(previous?.event_hash ?? '0'.repeat(64));
|
|
175
|
+
const eventHash = crypto.createHash('sha256').update(`${previousHash}\0${payload}`).digest('hex');
|
|
176
|
+
const newVersion = actualVersion + 1;
|
|
177
|
+
db.prepare(`INSERT INTO cognitive_events
|
|
178
|
+
(event_id, tenant_id, agent_id, expected_version, new_version, occurred_at, payload_json, previous_hash, event_hash)
|
|
179
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
180
|
+
.run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion, normalized.occurred_at, payload, previousHash, eventHash);
|
|
181
|
+
const insertObservation = db.prepare(`INSERT INTO cognitive_observations
|
|
182
|
+
(observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, scope, source_json, occurred_at)
|
|
183
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
184
|
+
const sourceJson = JSON.stringify(normalized.source);
|
|
185
|
+
for (const observation of normalized.observations) {
|
|
186
|
+
insertObservation.run(observation.observation_id, normalized.event_id, normalized.tenant_id, normalized.agent_id,
|
|
187
|
+
observation.domain, observation.key, observation.value, observation.confidence, observation.scope, sourceJson, normalized.occurred_at);
|
|
188
|
+
}
|
|
189
|
+
db.prepare(`INSERT INTO cognitive_streams (tenant_id, agent_id, version, updated_at) VALUES (?, ?, ?, ?)
|
|
190
|
+
ON CONFLICT(tenant_id, agent_id) DO UPDATE SET version = excluded.version, updated_at = excluded.updated_at`)
|
|
191
|
+
.run(normalized.tenant_id, normalized.agent_id, newVersion, normalized.occurred_at);
|
|
192
|
+
db.exec('COMMIT');
|
|
193
|
+
return { version: newVersion, event_hash: eventHash };
|
|
194
|
+
} catch (error) {
|
|
195
|
+
try { db.exec('ROLLBACK'); } catch {}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function read({ tenantId, agentId } = {}) {
|
|
201
|
+
const tenant = safeId(tenantId);
|
|
202
|
+
const agent = safeId(agentId);
|
|
203
|
+
if (!tenant || !agent) fail('COGNITIVE_INVALID_EVENT');
|
|
204
|
+
const stream = db.prepare('SELECT version, updated_at FROM cognitive_streams WHERE tenant_id = ? AND agent_id = ?').get(tenant, agent);
|
|
205
|
+
const rows = db.prepare(`SELECT domain, fact_key, value_text, confidence, scope, source_json, occurred_at, observation_id
|
|
206
|
+
FROM cognitive_observations WHERE tenant_id = ? AND agent_id = ? ORDER BY occurred_at, rowid`).all(tenant, agent);
|
|
207
|
+
return {
|
|
208
|
+
version: Number(stream?.version ?? 0),
|
|
209
|
+
...(stream?.updated_at ? { updated_at: stream.updated_at } : {}),
|
|
210
|
+
observations: rows.map((row) => ({
|
|
211
|
+
observation_id: row.observation_id,
|
|
212
|
+
domain: row.domain,
|
|
213
|
+
key: row.fact_key,
|
|
214
|
+
value: row.value_text,
|
|
215
|
+
confidence: row.confidence,
|
|
216
|
+
scope: row.scope,
|
|
217
|
+
source: JSON.parse(row.source_json),
|
|
218
|
+
occurred_at: row.occurred_at,
|
|
219
|
+
})),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function verify({ tenantId, agentId } = {}) {
|
|
224
|
+
const tenant = safeId(tenantId);
|
|
225
|
+
const agent = safeId(agentId);
|
|
226
|
+
if (!tenant || !agent) fail('COGNITIVE_INVALID_EVENT');
|
|
227
|
+
const rows = db.prepare(`SELECT payload_json, previous_hash, event_hash FROM cognitive_events
|
|
228
|
+
WHERE tenant_id = ? AND agent_id = ? ORDER BY new_version`).all(tenant, agent);
|
|
229
|
+
let previousHash = '0'.repeat(64);
|
|
230
|
+
for (const row of rows) {
|
|
231
|
+
const calculated = crypto.createHash('sha256').update(`${previousHash}\0${row.payload_json}`).digest('hex');
|
|
232
|
+
if (row.previous_hash !== previousHash || row.event_hash !== calculated) return { valid: false, events: rows.length };
|
|
233
|
+
previousHash = row.event_hash;
|
|
234
|
+
}
|
|
235
|
+
return { valid: true, events: rows.length, head_hash: previousHash };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
commit,
|
|
240
|
+
read,
|
|
241
|
+
verify,
|
|
242
|
+
close: () => {
|
|
243
|
+
try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch {}
|
|
244
|
+
db.close();
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = { openCognitiveStateStore };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
|
|
5
|
+
|
|
6
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
7
|
+
const RIGHTS_AUTHORITIES = new Set(['runtime_user_prompt_hook', 'runtime_tool_policy']);
|
|
8
|
+
const RIGHTS_DECISIONS = new Set(['passed', 'blocked', 'not_applicable']);
|
|
9
|
+
const TURN_REASONS = new Set(['completed', 'cancelled', 'filtered', 'failed']);
|
|
10
|
+
|
|
11
|
+
function fail(code) {
|
|
12
|
+
const error = new Error(code);
|
|
13
|
+
error.code = code;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function exactKeys(value, keys) {
|
|
18
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
19
|
+
&& Object.keys(value).every((key) => keys.has(key));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function safeId(value) {
|
|
23
|
+
const text = String(value ?? '').trim();
|
|
24
|
+
return SAFE_ID_RE.test(text) ? text : '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function safeTurnId(value) {
|
|
28
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function digestId(prefix, values) {
|
|
32
|
+
const digest = crypto.createHash('sha256').update(values.join('\0')).digest('hex').slice(0, 40);
|
|
33
|
+
return `${prefix}-${digest}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createCognitiveTurnLifecycle({ home, tenantId, agentId, runtimeId, now = () => new Date().toISOString() } = {}) {
|
|
37
|
+
const tenant = safeId(tenantId);
|
|
38
|
+
const agent = safeId(agentId);
|
|
39
|
+
const runtime = safeId(runtimeId ?? `runtime-${process.pid}-${Date.now()}`);
|
|
40
|
+
if (!tenant || !agent || !runtime || typeof now !== 'function') fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
41
|
+
const store = openCognitiveStateStore({ home });
|
|
42
|
+
const stageTimes = new Map();
|
|
43
|
+
const stageResults = new Map();
|
|
44
|
+
|
|
45
|
+
function stageTime(stageKey) {
|
|
46
|
+
if (!stageTimes.has(stageKey)) {
|
|
47
|
+
const occurredAt = String(now());
|
|
48
|
+
if (Number.isNaN(Date.parse(occurredAt))) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
49
|
+
stageTimes.set(stageKey, occurredAt);
|
|
50
|
+
}
|
|
51
|
+
return stageTimes.get(stageKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function commitStage(stageKey, observations) {
|
|
55
|
+
if (stageResults.has(stageKey)) return { ...stageResults.get(stageKey), idempotent: true };
|
|
56
|
+
const occurredAt = stageTime(stageKey);
|
|
57
|
+
const eventId = digestId('cycle', [tenant, agent, runtime, stageKey]);
|
|
58
|
+
const normalized = observations.map((item, index) => ({
|
|
59
|
+
observation_id: digestId('cycleobs', [eventId, String(index)]),
|
|
60
|
+
...item,
|
|
61
|
+
}));
|
|
62
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
63
|
+
const expectedVersion = store.read({ tenantId: tenant, agentId: agent }).version;
|
|
64
|
+
try {
|
|
65
|
+
const result = store.commit({
|
|
66
|
+
tenantId: tenant,
|
|
67
|
+
agentId: agent,
|
|
68
|
+
eventId,
|
|
69
|
+
expectedVersion,
|
|
70
|
+
occurredAt,
|
|
71
|
+
source: {
|
|
72
|
+
provider: 'runtime',
|
|
73
|
+
actor_id: agent,
|
|
74
|
+
context_id: runtime,
|
|
75
|
+
message_id: stageKey,
|
|
76
|
+
},
|
|
77
|
+
observations: normalized,
|
|
78
|
+
});
|
|
79
|
+
stageResults.set(stageKey, result);
|
|
80
|
+
return result;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error?.code !== 'COGNITIVE_VERSION_CONFLICT' || attempt === 3) throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
fail('COGNITIVE_VERSION_CONFLICT');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function startTurn(input) {
|
|
89
|
+
if (!exactKeys(input, new Set(['turnId', 'originKind']))) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
90
|
+
const turnId = safeTurnId(input.turnId);
|
|
91
|
+
const originKind = safeId(input.originKind);
|
|
92
|
+
if (turnId === null || !originKind) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
93
|
+
const key = `turn:${turnId}`;
|
|
94
|
+
return commitStage(`turn-${turnId}-start`, [
|
|
95
|
+
{ domain: 'open_thread', key: `${key}:phase`, value: 'perceived', confidence: 1, scope: 'runtime' },
|
|
96
|
+
{ domain: 'assumption', key: `${key}:classification`, value: `origin:${originKind}`, confidence: 1, scope: 'runtime' },
|
|
97
|
+
{ domain: 'goal', key: `${key}:plan`, value: 'run-bounded-model-turn', confidence: 1, scope: 'runtime' },
|
|
98
|
+
{ domain: 'next_trigger', key: `${key}:next`, value: 'runtime-policy-check', confidence: 1, scope: 'runtime' },
|
|
99
|
+
{ domain: 'expected_evidence', key: `${key}:evidence`, value: 'turn.ended-event', confidence: 1, scope: 'runtime' },
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function recordRightsCheck(input) {
|
|
104
|
+
if (!exactKeys(input, new Set(['turnId', 'decision', 'authority']))) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
105
|
+
const turnId = safeTurnId(input.turnId);
|
|
106
|
+
const decision = String(input.decision ?? '');
|
|
107
|
+
const authority = String(input.authority ?? '');
|
|
108
|
+
if (turnId === null || !RIGHTS_DECISIONS.has(decision)) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
109
|
+
if (!RIGHTS_AUTHORITIES.has(authority)) fail('COGNITIVE_RIGHTS_AUTHORITY_REQUIRED');
|
|
110
|
+
return commitStage(`turn-${turnId}-rights-${authority}`, [{
|
|
111
|
+
domain: 'world',
|
|
112
|
+
key: `turn:${turnId}:rights-check`,
|
|
113
|
+
value: `${decision}:${authority}`,
|
|
114
|
+
confidence: 1,
|
|
115
|
+
scope: 'runtime',
|
|
116
|
+
}]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function endTurn(input) {
|
|
120
|
+
if (!exactKeys(input, new Set(['turnId', 'reason', 'durationMs']))) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
121
|
+
const turnId = safeTurnId(input.turnId);
|
|
122
|
+
const reason = String(input.reason ?? '');
|
|
123
|
+
const durationMs = Number(input.durationMs);
|
|
124
|
+
if (turnId === null || !TURN_REASONS.has(reason) || !Number.isSafeInteger(durationMs) || durationMs < 0) {
|
|
125
|
+
fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
126
|
+
}
|
|
127
|
+
const key = `turn:${turnId}`;
|
|
128
|
+
return commitStage(`turn-${turnId}-end`, [
|
|
129
|
+
{ domain: 'open_thread', key: `${key}:phase`, value: reason, confidence: 1, scope: 'runtime' },
|
|
130
|
+
{ domain: 'expected_evidence', key: `${key}:result`, value: `turn.ended:${reason}:${durationMs}ms`, confidence: 1, scope: 'runtime' },
|
|
131
|
+
{ domain: 'next_trigger', key: `${key}:next`, value: reason === 'completed' ? 'await-next-input' : 'inspect-turn-outcome', confidence: 1, scope: 'runtime' },
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
startTurn,
|
|
137
|
+
recordRightsCheck,
|
|
138
|
+
endTurn,
|
|
139
|
+
read: () => store.read({ tenantId: tenant, agentId: agent }),
|
|
140
|
+
verify: () => store.verify({ tenantId: tenant, agentId: agent }),
|
|
141
|
+
close: () => store.close(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createRuntimeCognitiveTurnLifecycle({ home, agentName, runtimeId, now } = {}) {
|
|
146
|
+
const resolvedHome = String(home ?? '').trim();
|
|
147
|
+
const resolvedAgent = String(agentName ?? '').trim();
|
|
148
|
+
if (!resolvedHome || !resolvedAgent) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
149
|
+
return createCognitiveTurnLifecycle({
|
|
150
|
+
home: resolvedHome,
|
|
151
|
+
tenantId: digestId('home', [resolvedHome.toLowerCase()]),
|
|
152
|
+
agentId: digestId('agent', [resolvedAgent.toLowerCase()]),
|
|
153
|
+
runtimeId,
|
|
154
|
+
now,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = { createCognitiveTurnLifecycle, createRuntimeCognitiveTurnLifecycle };
|
package/blun.mjs
CHANGED
|
@@ -261391,7 +261391,7 @@ function toolResultText(result) {
|
|
|
261391
261391
|
function abandonedToolResultOutput(ended) {
|
|
261392
261392
|
return `Tool call did not complete: ${ended.reason === "cancelled" ? "the turn was cancelled" : ended.reason === "failed" ? `the turn failed${ended.error !== void 0 ? ` (${ended.error.message})` : ""}` : "the turn ended"} before its result was recorded. Do not assume the tool completed successfully.`;
|
|
261393
261393
|
}
|
|
261394
|
-
var BLUN_CORE_TOOL_NAMES, BLUN_LEAN_TOOL_NAMES, BLUN_ATTACHMENT_MARKER_RE, BLUN_TELEGRAM_OUTBOUND_TOOL_RE, BLUN_TELEGRAM_CHANNEL_RE, BLUN_TOOL_BUDGET_RATIO, createDeferredToolLoader, mediaGenerationToolNamesForText, rememberDeferredToolAfterNotFound, toolSchemaBudgetTokens, projectRecurringCronHistory, TelegramDeliveryLedger, LLM_NOT_SET_MESSAGE, GOAL_CONTINUATION_ORIGIN, GOAL_COMPLETION_REMINDER_NAME, GOAL_BLOCKED_REMINDER_NAME, GOAL_RATE_LIMIT_PAUSE_REASON, GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX, GOAL_PROVIDER_AUTH_PAUSE_PREFIX, GOAL_PROVIDER_API_PAUSE_PREFIX, GOAL_MODEL_CONFIG_PAUSE_PREFIX, GOAL_RUNTIME_PAUSE_PREFIX, GOAL_PROVIDER_FILTERED_PAUSE_REASON, GOAL_CONTINUATION_PROMPT, TurnFlow;
|
|
261394
|
+
var BLUN_CORE_TOOL_NAMES, BLUN_LEAN_TOOL_NAMES, BLUN_ATTACHMENT_MARKER_RE, BLUN_TELEGRAM_OUTBOUND_TOOL_RE, BLUN_TELEGRAM_CHANNEL_RE, BLUN_TOOL_BUDGET_RATIO, createDeferredToolLoader, mediaGenerationToolNamesForText, rememberDeferredToolAfterNotFound, toolSchemaBudgetTokens, projectRecurringCronHistory, TelegramDeliveryLedger, createRuntimeCognitiveTurnLifecycle, LLM_NOT_SET_MESSAGE, GOAL_CONTINUATION_ORIGIN, GOAL_COMPLETION_REMINDER_NAME, GOAL_BLOCKED_REMINDER_NAME, GOAL_RATE_LIMIT_PAUSE_REASON, GOAL_PROVIDER_CONNECTION_PAUSE_PREFIX, GOAL_PROVIDER_AUTH_PAUSE_PREFIX, GOAL_PROVIDER_API_PAUSE_PREFIX, GOAL_MODEL_CONFIG_PAUSE_PREFIX, GOAL_RUNTIME_PAUSE_PREFIX, GOAL_PROVIDER_FILTERED_PAUSE_REASON, GOAL_CONTINUATION_PROMPT, TurnFlow;
|
|
261395
261395
|
var init_turn = __esmMin((() => {
|
|
261396
261396
|
init_dist$4();
|
|
261397
261397
|
init_src$4();
|
|
@@ -261411,6 +261411,7 @@ var init_turn = __esmMin((() => {
|
|
|
261411
261411
|
({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, mediaGenerationToolNamesForText, rememberDeferredToolAfterNotFound, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
|
|
261412
261412
|
({ projectRecurringCronHistory } = createRequire(import.meta.url)("./bin/recurring-cron-history-policy.cjs"));
|
|
261413
261413
|
({ TelegramDeliveryLedger } = createRequire(import.meta.url)("./bin/repeated-user-message-projection.cjs"));
|
|
261414
|
+
({ createRuntimeCognitiveTurnLifecycle } = createRequire(import.meta.url)("./bin/cognitive-turn-lifecycle.cjs"));
|
|
261414
261415
|
BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
|
|
261415
261416
|
BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
|
|
261416
261417
|
BLUN_TELEGRAM_OUTBOUND_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__(?:reply|react|edit_message)$/i;
|
|
@@ -261471,6 +261472,8 @@ var init_turn = __esmMin((() => {
|
|
|
261471
261472
|
interruptedTelemetryTurnIds = /* @__PURE__ */ new Set();
|
|
261472
261473
|
stepFailureByTurn = /* @__PURE__ */ new Map();
|
|
261473
261474
|
currentStep = 0;
|
|
261475
|
+
cognitiveLifecycle;
|
|
261476
|
+
cognitiveLifecycleUnavailable = false;
|
|
261474
261477
|
constructor(agent) {
|
|
261475
261478
|
this.agent = agent;
|
|
261476
261479
|
}
|
|
@@ -261478,6 +261481,33 @@ var init_turn = __esmMin((() => {
|
|
|
261478
261481
|
get agentId() {
|
|
261479
261482
|
return this.agent.homedir ? basename$2(this.agent.homedir) : this.agent.type;
|
|
261480
261483
|
}
|
|
261484
|
+
getCognitiveLifecycle() {
|
|
261485
|
+
if (this.cognitiveLifecycleUnavailable) return null;
|
|
261486
|
+
if (this.cognitiveLifecycle !== void 0) return this.cognitiveLifecycle;
|
|
261487
|
+
const home = this.agent.blunHomeDir ?? this.agent.homedir;
|
|
261488
|
+
if (home === void 0) {
|
|
261489
|
+
this.cognitiveLifecycleUnavailable = true;
|
|
261490
|
+
return null;
|
|
261491
|
+
}
|
|
261492
|
+
try {
|
|
261493
|
+
this.cognitiveLifecycle = createRuntimeCognitiveTurnLifecycle({
|
|
261494
|
+
home,
|
|
261495
|
+
agentName: this.agent.activeProfile?.name ?? this.agent.type
|
|
261496
|
+
});
|
|
261497
|
+
return this.cognitiveLifecycle;
|
|
261498
|
+
} catch (error) {
|
|
261499
|
+
this.cognitiveLifecycleUnavailable = true;
|
|
261500
|
+
this.agent.telemetry.track("cognitive_lifecycle_error", { stage: "open", error_type: error?.code ?? error?.name ?? "Error" });
|
|
261501
|
+
return null;
|
|
261502
|
+
}
|
|
261503
|
+
}
|
|
261504
|
+
recordCognitiveStage(method, input) {
|
|
261505
|
+
try {
|
|
261506
|
+
this.getCognitiveLifecycle()?.[method](input);
|
|
261507
|
+
} catch (error) {
|
|
261508
|
+
this.agent.telemetry.track("cognitive_lifecycle_error", { stage: method, error_type: error?.code ?? error?.name ?? "Error" });
|
|
261509
|
+
}
|
|
261510
|
+
}
|
|
261481
261511
|
prompt(input, origin = USER_PROMPT_ORIGIN) {
|
|
261482
261512
|
return this.promptWithAcceptance(input, origin).turnId;
|
|
261483
261513
|
}
|
|
@@ -261805,6 +261835,12 @@ var init_turn = __esmMin((() => {
|
|
|
261805
261835
|
origin
|
|
261806
261836
|
});
|
|
261807
261837
|
this.agent.context.appendUserMessage(input, origin);
|
|
261838
|
+
this.recordCognitiveStage("startTurn", { turnId, originKind: origin.kind });
|
|
261839
|
+
this.recordCognitiveStage("recordRightsCheck", {
|
|
261840
|
+
turnId,
|
|
261841
|
+
decision: "not_applicable",
|
|
261842
|
+
authority: "runtime_user_prompt_hook"
|
|
261843
|
+
});
|
|
261808
261844
|
const ended = {
|
|
261809
261845
|
type: "turn.ended",
|
|
261810
261846
|
turnId,
|
|
@@ -261812,6 +261848,7 @@ var init_turn = __esmMin((() => {
|
|
|
261812
261848
|
durationMs: Date.now() - startedAt
|
|
261813
261849
|
};
|
|
261814
261850
|
this.agent.usage.endTurn();
|
|
261851
|
+
this.recordCognitiveStage("endTurn", { turnId, reason: ended.reason, durationMs: ended.durationMs ?? 0 });
|
|
261815
261852
|
this.agent.emitEvent(ended);
|
|
261816
261853
|
return ended;
|
|
261817
261854
|
}
|
|
@@ -261841,6 +261878,7 @@ var init_turn = __esmMin((() => {
|
|
|
261841
261878
|
origin
|
|
261842
261879
|
});
|
|
261843
261880
|
this.agent.context.appendUserMessage(input, origin);
|
|
261881
|
+
this.recordCognitiveStage("startTurn", { turnId, originKind: origin.kind });
|
|
261844
261882
|
const startedAt = Date.now();
|
|
261845
261883
|
let ended;
|
|
261846
261884
|
let blockedByUserPromptHook = false;
|
|
@@ -261848,6 +261886,11 @@ var init_turn = __esmMin((() => {
|
|
|
261848
261886
|
let errorEvent;
|
|
261849
261887
|
try {
|
|
261850
261888
|
const promptHookEnded = await this.applyUserPromptHook(turnId, input, origin, signal, startedAt);
|
|
261889
|
+
this.recordCognitiveStage("recordRightsCheck", {
|
|
261890
|
+
turnId,
|
|
261891
|
+
decision: origin.kind !== "user" ? "not_applicable" : promptHookEnded?.blocked === true ? "blocked" : "passed",
|
|
261892
|
+
authority: "runtime_user_prompt_hook"
|
|
261893
|
+
});
|
|
261851
261894
|
if (promptHookEnded !== void 0) {
|
|
261852
261895
|
ended = promptHookEnded.event;
|
|
261853
261896
|
blockedByUserPromptHook = promptHookEnded.blocked;
|
|
@@ -261919,6 +261962,7 @@ var init_turn = __esmMin((() => {
|
|
|
261919
261962
|
mode: this.telemetryModeByTurn.get(turnId) ?? this.telemetryMode(),
|
|
261920
261963
|
...this.requestProviderProps()
|
|
261921
261964
|
});
|
|
261965
|
+
this.recordCognitiveStage("endTurn", { turnId, reason: ended.reason, durationMs: ended.durationMs ?? 0 });
|
|
261922
261966
|
this.agent.emitEvent(ended);
|
|
261923
261967
|
this.agent.endResponderTurn();
|
|
261924
261968
|
if (standalone && this.currentId === turnId && this.agent.goal.getGoal().goal?.status !== "active") this.activeTurn = null;
|