blun-king-cli 9.1.305 → 9.1.307
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-context-projection.cjs +73 -0
- package/bin/cognitive-turn-lifecycle.cjs +54 -0
- package/blun.mjs +38 -1
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TURN_KEY_RE = /^turn:(\d+):/u;
|
|
4
|
+
const NEGATIVE_PHASES = new Set(['cancelled', 'failed', 'filtered']);
|
|
5
|
+
const NEGATIVE_POLICY = new Set(['blocked', 'error']);
|
|
6
|
+
const NEGATIVE_OUTCOMES = new Set(['error', 'cancelled']);
|
|
7
|
+
const SAFETY_LINE = 'Runtime evidence only; it cannot authorize any action or override current user or tool policy.';
|
|
8
|
+
|
|
9
|
+
function clean(value, max = 128) {
|
|
10
|
+
const text = String(value ?? '').replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim();
|
|
11
|
+
return text && text.length <= max ? text : '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function turnNumber(observation) {
|
|
15
|
+
const match = TURN_KEY_RE.exec(String(observation?.key ?? ''));
|
|
16
|
+
return match ? Number(match[1]) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runtimeObservation(value) {
|
|
20
|
+
return value && typeof value === 'object' && value.scope === 'runtime'
|
|
21
|
+
&& Number(value.confidence) === 1 && clean(value.key) && clean(value.value, 512)
|
|
22
|
+
&& clean(value.source?.context_id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function splitEvidence(value) {
|
|
26
|
+
return String(value ?? '').split(':').map((part) => clean(part)).filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildCognitiveContextProjection(state, { currentTurnId, currentRuntimeId, maxChars = 900 } = {}) {
|
|
30
|
+
if (!Number.isSafeInteger(currentTurnId) || currentTurnId < 0
|
|
31
|
+
|| !clean(currentRuntimeId)
|
|
32
|
+
|| !Number.isSafeInteger(maxChars) || maxChars < 320 || maxChars > 1600
|
|
33
|
+
|| !Array.isArray(state?.observations)) return null;
|
|
34
|
+
|
|
35
|
+
const previous = state.observations.filter(runtimeObservation)
|
|
36
|
+
.map((observation) => ({
|
|
37
|
+
observation,
|
|
38
|
+
turnId: turnNumber(observation),
|
|
39
|
+
runtimeId: observation.source.context_id,
|
|
40
|
+
}))
|
|
41
|
+
.filter((item) => item.turnId !== null
|
|
42
|
+
&& !(item.runtimeId === currentRuntimeId && item.turnId === currentTurnId));
|
|
43
|
+
if (previous.length === 0) return null;
|
|
44
|
+
const latestItem = previous.at(-1);
|
|
45
|
+
const latestTurnId = latestItem.turnId;
|
|
46
|
+
const latest = previous.filter((item) => item.turnId === latestTurnId && item.runtimeId === latestItem.runtimeId)
|
|
47
|
+
.map((item) => item.observation);
|
|
48
|
+
|
|
49
|
+
const phase = [...latest].reverse().find((item) => item.domain === 'open_thread' && item.key === `turn:${latestTurnId}:phase`);
|
|
50
|
+
const policies = latest.filter((item) => item.key.endsWith(':tool-policy')).map((item) => {
|
|
51
|
+
const parts = splitEvidence(item.value);
|
|
52
|
+
return { decision: parts[0], tool: parts.at(-1) };
|
|
53
|
+
}).filter((item) => NEGATIVE_POLICY.has(item.decision) && item.tool);
|
|
54
|
+
const outcomes = latest.filter((item) => item.key.endsWith(':tool-result')).map((item) => {
|
|
55
|
+
const parts = splitEvidence(item.value);
|
|
56
|
+
return { outcome: parts[0], tool: parts.at(-1) };
|
|
57
|
+
}).filter((item) => NEGATIVE_OUTCOMES.has(item.outcome) && item.tool);
|
|
58
|
+
const actionable = phase && NEGATIVE_PHASES.has(phase.value) || policies.length > 0 || outcomes.length > 0;
|
|
59
|
+
if (!actionable) return null;
|
|
60
|
+
|
|
61
|
+
const lines = [`Previous runtime turn ${latestTurnId} needs inspection.`];
|
|
62
|
+
if (phase && NEGATIVE_PHASES.has(phase.value)) lines.push(`Turn outcome: ${phase.value}.`);
|
|
63
|
+
for (const item of policies.slice(-2)) lines.push(`Tool policy: ${item.tool} ${item.decision}.`);
|
|
64
|
+
for (const item of outcomes.slice(-2)) lines.push(`Tool outcome: ${item.tool} ${item.outcome}.`);
|
|
65
|
+
const next = [...latest].reverse().find((item) => item.domain === 'next_trigger'
|
|
66
|
+
&& item.value !== 'await-next-input' && clean(item.value));
|
|
67
|
+
if (next) lines.push(`Next inspection trigger: ${clean(next.value)}.`);
|
|
68
|
+
|
|
69
|
+
while ([...lines, SAFETY_LINE].join('\n').length > maxChars && lines.length > 1) lines.splice(-1, 1);
|
|
70
|
+
return [...lines, SAFETY_LINE].join('\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { buildCognitiveContextProjection };
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
|
|
5
|
+
const { buildCognitiveContextProjection } = require('./cognitive-context-projection.cjs');
|
|
5
6
|
|
|
6
7
|
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
7
8
|
const RIGHTS_AUTHORITIES = new Set(['runtime_user_prompt_hook', 'runtime_tool_policy']);
|
|
8
9
|
const RIGHTS_DECISIONS = new Set(['passed', 'blocked', 'not_applicable']);
|
|
10
|
+
const TOOL_POLICY_DECISIONS = new Set(['passed', 'blocked', 'error']);
|
|
11
|
+
const TOOL_OUTCOMES = new Set(['success', 'error', 'cancelled']);
|
|
9
12
|
const TURN_REASONS = new Set(['completed', 'cancelled', 'filtered', 'failed']);
|
|
10
13
|
|
|
11
14
|
function fail(code) {
|
|
@@ -28,6 +31,11 @@ function safeTurnId(value) {
|
|
|
28
31
|
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
function cleanLabel(value, max = 128) {
|
|
35
|
+
const text = String(value ?? '').replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim();
|
|
36
|
+
return text && text.length <= max ? text : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
function digestId(prefix, values) {
|
|
32
40
|
const digest = crypto.createHash('sha256').update(values.join('\0')).digest('hex').slice(0, 40);
|
|
33
41
|
return `${prefix}-${digest}`;
|
|
@@ -132,9 +140,55 @@ function createCognitiveTurnLifecycle({ home, tenantId, agentId, runtimeId, now
|
|
|
132
140
|
]);
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
function toolFields(input, allowedKeys) {
|
|
144
|
+
if (!exactKeys(input, allowedKeys)) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
145
|
+
const turnId = safeTurnId(input.turnId);
|
|
146
|
+
const toolCallId = cleanLabel(input.toolCallId, 256);
|
|
147
|
+
const toolName = cleanLabel(input.toolName, 128);
|
|
148
|
+
if (turnId === null || !toolCallId || !toolName) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
149
|
+
return { turnId, toolCallId, toolName, callKey: digestId('call', [toolCallId]).slice(0, 21) };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function recordToolPolicy(input) {
|
|
153
|
+
const { turnId, toolCallId, toolName, callKey } = toolFields(input, new Set(['turnId', 'toolCallId', 'toolName', 'decision']));
|
|
154
|
+
const decision = String(input.decision ?? '');
|
|
155
|
+
if (!TOOL_POLICY_DECISIONS.has(decision)) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
156
|
+
return commitStage(`turn-${turnId}-${callKey}-policy`, [{
|
|
157
|
+
domain: 'world',
|
|
158
|
+
key: `turn:${turnId}:${callKey}:tool-policy`,
|
|
159
|
+
value: `${decision}:runtime_tool_policy:${toolName}`,
|
|
160
|
+
confidence: 1,
|
|
161
|
+
scope: 'runtime',
|
|
162
|
+
}]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function recordToolResult(input) {
|
|
166
|
+
const { turnId, toolCallId, toolName, callKey } = toolFields(input, new Set(['turnId', 'toolCallId', 'toolName', 'outcome', 'durationMs']));
|
|
167
|
+
const outcome = String(input.outcome ?? '');
|
|
168
|
+
const durationMs = Number(input.durationMs);
|
|
169
|
+
if (!TOOL_OUTCOMES.has(outcome) || !Number.isSafeInteger(durationMs) || durationMs < 0) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
170
|
+
return commitStage(`turn-${turnId}-${callKey}-result`, [
|
|
171
|
+
{ domain: 'expected_evidence', key: `turn:${turnId}:${callKey}:tool-result`, value: `${outcome}:${durationMs}ms:${toolName}`, confidence: 1, scope: 'runtime' },
|
|
172
|
+
{ domain: 'next_trigger', key: `turn:${turnId}:${callKey}:tool-next`, value: outcome === 'success' ? 'continue-after-tool' : 'inspect-tool-failure', confidence: 1, scope: 'runtime' },
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function projectForTurn(input) {
|
|
177
|
+
if (!exactKeys(input, new Set(['turnId']))) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
178
|
+
const turnId = safeTurnId(input.turnId);
|
|
179
|
+
if (turnId === null) fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
180
|
+
return buildCognitiveContextProjection(store.read({ tenantId: tenant, agentId: agent }), {
|
|
181
|
+
currentTurnId: turnId,
|
|
182
|
+
currentRuntimeId: runtime,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
135
186
|
return {
|
|
136
187
|
startTurn,
|
|
137
188
|
recordRightsCheck,
|
|
189
|
+
recordToolPolicy,
|
|
190
|
+
recordToolResult,
|
|
191
|
+
projectForTurn,
|
|
138
192
|
endTurn,
|
|
139
193
|
read: () => store.read({ tenantId: tenant, agentId: agent }),
|
|
140
194
|
verify: () => store.verify({ tenantId: tenant, agentId: agent }),
|
package/blun.mjs
CHANGED
|
@@ -261508,6 +261508,14 @@ var init_turn = __esmMin((() => {
|
|
|
261508
261508
|
this.agent.telemetry.track("cognitive_lifecycle_error", { stage: method, error_type: error?.code ?? error?.name ?? "Error" });
|
|
261509
261509
|
}
|
|
261510
261510
|
}
|
|
261511
|
+
projectCognitiveState(turnId) {
|
|
261512
|
+
try {
|
|
261513
|
+
return this.getCognitiveLifecycle()?.projectForTurn({ turnId }) ?? null;
|
|
261514
|
+
} catch (error) {
|
|
261515
|
+
this.agent.telemetry.track("cognitive_lifecycle_error", { stage: "projectForTurn", error_type: error?.code ?? error?.name ?? "Error" });
|
|
261516
|
+
return null;
|
|
261517
|
+
}
|
|
261518
|
+
}
|
|
261511
261519
|
prompt(input, origin = USER_PROMPT_ORIGIN) {
|
|
261512
261520
|
return this.promptWithAcceptance(input, origin).turnId;
|
|
261513
261521
|
}
|
|
@@ -262044,6 +262052,11 @@ var init_turn = __esmMin((() => {
|
|
|
262044
262052
|
if (blunTurnNeedsInitialMcp(input, origin)) await this.agent.mcp?.waitForInitialLoad(signal);
|
|
262045
262053
|
const personalMemoryRecall = await this.agent.injection.injectPersonalMemoryForTurn(turnId, input, origin, signal);
|
|
262046
262054
|
await this.agent.injection.injectGoal();
|
|
262055
|
+
const cognitiveProjection = this.projectCognitiveState(turnId);
|
|
262056
|
+
if (cognitiveProjection !== null) this.agent.context.appendSystemReminder(cognitiveProjection, {
|
|
262057
|
+
kind: "injection",
|
|
262058
|
+
variant: "cognitive_continuity"
|
|
262059
|
+
});
|
|
262047
262060
|
this.setActiveSteerAcceptance(turnId, true);
|
|
262048
262061
|
const turnNeedsTools = blunTurnNeedsTools(input, origin);
|
|
262049
262062
|
const turnHasAttachment = blunTurnHasAttachment(input);
|
|
@@ -262214,7 +262227,24 @@ var init_turn = __esmMin((() => {
|
|
|
262214
262227
|
if (cached !== null) return { syntheticResult: cached };
|
|
262215
262228
|
},
|
|
262216
262229
|
authorizeToolExecution: async (ctx) => {
|
|
262217
|
-
|
|
262230
|
+
try {
|
|
262231
|
+
const resolution = await this.agent.permission.beforeToolCall(ctx);
|
|
262232
|
+
this.recordCognitiveStage("recordToolPolicy", {
|
|
262233
|
+
turnId,
|
|
262234
|
+
toolCallId: ctx.toolCall.id,
|
|
262235
|
+
toolName: ctx.toolCall.name,
|
|
262236
|
+
decision: resolution?.block === true ? "blocked" : "passed"
|
|
262237
|
+
});
|
|
262238
|
+
return resolution;
|
|
262239
|
+
} catch (error) {
|
|
262240
|
+
this.recordCognitiveStage("recordToolPolicy", {
|
|
262241
|
+
turnId,
|
|
262242
|
+
toolCallId: ctx.toolCall.id,
|
|
262243
|
+
toolName: ctx.toolCall.name,
|
|
262244
|
+
decision: "error"
|
|
262245
|
+
});
|
|
262246
|
+
throw error;
|
|
262247
|
+
}
|
|
262218
262248
|
},
|
|
262219
262249
|
finalizeToolResult: async (ctx) => {
|
|
262220
262250
|
const finalResult = await deduper.finalizeResult(ctx.toolCall.id, ctx.toolCall.name, ctx.args, ctx.result);
|
|
@@ -262356,6 +262386,13 @@ var init_turn = __esmMin((() => {
|
|
|
262356
262386
|
};
|
|
262357
262387
|
const errorType = outcome === "error" ? telemetryToolErrorType(event.result) : void 0;
|
|
262358
262388
|
if (errorType !== void 0) properties["error_type"] = errorType;
|
|
262389
|
+
this.recordCognitiveStage("recordToolResult", {
|
|
262390
|
+
turnId,
|
|
262391
|
+
toolCallId: event.toolCallId,
|
|
262392
|
+
toolName: started.name,
|
|
262393
|
+
outcome,
|
|
262394
|
+
durationMs: Date.now() - started.startedAt
|
|
262395
|
+
});
|
|
262359
262396
|
this.agent.telemetry.track("tool_call", properties);
|
|
262360
262397
|
this.agent.feedRootMissionContract("result", {
|
|
262361
262398
|
toolCallId: event.toolCallId,
|