blun-king-cli 9.1.446 → 9.1.448

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.
@@ -13,19 +13,26 @@ const TRIGGER_KINDS = new Set([
13
13
  'immediate', 'external_event', 'time', 'dependency', 'user_decision',
14
14
  ]);
15
15
  const MODEL_KEYS = new Set([
16
- 'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'nextTrigger', 'problemFrame', 'updatedAt',
16
+ 'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'verificationProof', 'nextTrigger', 'problemFrame', 'updatedAt',
17
17
  ]);
18
18
  const RUNTIME_KEYS = new Set([...MODEL_KEYS, 'evidenceReceipt']);
19
19
  const PROBLEM_FRAME_KEYS = new Set([
20
20
  'successCriterion', 'missingKnowledge', 'candidateActions', 'selectedAction',
21
- 'selectionReason', 'supportChoice', 'risk', 'reversibility',
21
+ 'selectionReason', 'supportChoice', 'risk', 'reversibility', 'decisionBasis',
22
22
  ]);
23
23
  const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
24
24
  const EVIDENCE_INPUT_KEYS = new Set([
25
25
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
26
26
  ]);
27
27
  const EVIDENCE_DIGEST_RE = /^[a-f0-9]{16}$/u;
28
+ const DECISION_BASIS_RE = /^[a-f0-9]{16}$/u;
28
29
  const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u;
30
+ const ACTION_ONLY_TOOL_NAMES = new Set([
31
+ 'CreateGoal', 'CronCreate', 'CronDelete', 'DubVideo', 'Edit', 'EnterPlanMode',
32
+ 'ExitPlanMode', 'GenerateImage', 'GenerateSpeech', 'GenerateVideo', 'LipSyncMedia',
33
+ 'MistakeRecord', 'SetGoalBudget', 'TaskStop', 'TaskUpdate', 'UpdateGoal', 'Write',
34
+ ]);
35
+ const MAX_SUCCESSFUL_TOOL_DIGESTS = 32;
29
36
 
30
37
  function bounded(value, field, max = 512) {
31
38
  const text = String(value ?? '')
@@ -60,6 +67,15 @@ function boundedList(value, field) {
60
67
  return Object.freeze(value.map((item, index) => bounded(item, `${field}[${index}]`, 256)));
61
68
  }
62
69
 
70
+ function normalizedDecisionBasis(value) {
71
+ if (!Array.isArray(value) || value.length < 1 || value.length > 5
72
+ || value.some((item) => typeof item !== 'string' || !DECISION_BASIS_RE.test(item))
73
+ || new Set(value).size !== value.length) {
74
+ throw new TypeError('decisionBasis must contain between 1 and 5 unique evidence refs');
75
+ }
76
+ return Object.freeze([...value]);
77
+ }
78
+
63
79
  function normalizeProblemFrame(input) {
64
80
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
65
81
  throw new TypeError('problemFrame must be an object');
@@ -73,7 +89,7 @@ function normalizeProblemFrame(input) {
73
89
  if (!candidateActions.includes(selectedAction)) {
74
90
  throw new TypeError('selectedAction must match a candidateAction');
75
91
  }
76
- return Object.freeze({
92
+ const frame = {
77
93
  successCriterion: bounded(input.successCriterion, 'successCriterion'),
78
94
  missingKnowledge,
79
95
  candidateActions,
@@ -82,7 +98,9 @@ function normalizeProblemFrame(input) {
82
98
  supportChoice: bounded(input.supportChoice, 'supportChoice', 256),
83
99
  risk: bounded(input.risk, 'risk'),
84
100
  reversibility: bounded(input.reversibility, 'reversibility'),
85
- });
101
+ };
102
+ if (input.decisionBasis !== undefined) frame.decisionBasis = normalizedDecisionBasis(input.decisionBasis);
103
+ return Object.freeze(frame);
86
104
  }
87
105
 
88
106
  function normalizeNextTrigger(input, phase) {
@@ -136,6 +154,44 @@ function normalizedTurnId(value) {
136
154
  return turnId;
137
155
  }
138
156
 
157
+ function successfulToolDigest(toolName) {
158
+ const name = bounded(toolName, 'verificationProof toolName', 128);
159
+ return crypto.createHash('sha256').update(`tool:${name}`).digest('hex').slice(0, 16);
160
+ }
161
+
162
+ function isActionOnlyTool(toolName) {
163
+ return ACTION_ONLY_TOOL_NAMES.has(String(toolName ?? '').trim());
164
+ }
165
+
166
+ function normalizeSuccessfulToolDigests(value) {
167
+ if (value === undefined) return Object.freeze([]);
168
+ if (!Array.isArray(value) || value.length > MAX_SUCCESSFUL_TOOL_DIGESTS
169
+ || value.some((item) => typeof item !== 'string' || !EVIDENCE_DIGEST_RE.test(item))
170
+ || new Set(value).size !== value.length) {
171
+ throw new TypeError('successful tool digests are invalid');
172
+ }
173
+ return Object.freeze([...value]);
174
+ }
175
+
176
+ function normalizeVerificationProof(input, evidenceReceipt) {
177
+ if (!input || typeof input !== 'object' || Array.isArray(input)
178
+ || Object.keys(input).length !== 2
179
+ || !Object.hasOwn(input, 'toolName')
180
+ || !Object.hasOwn(input, 'claim')) {
181
+ throw new TypeError('verificationProof fields are invalid');
182
+ }
183
+ const toolName = bounded(input.toolName, 'verificationProof toolName', 128);
184
+ const claim = bounded(input.claim, 'verificationProof claim');
185
+ if (isActionOnlyTool(toolName)) {
186
+ throw new TypeError('action-only tool cannot serve as verification proof');
187
+ }
188
+ const receipt = normalizeActionEvidenceReceipt(evidenceReceipt);
189
+ if (!receipt.successfulToolDigests.includes(successfulToolDigest(toolName))) {
190
+ throw new TypeError('verificationProof must name a successful current-turn tool');
191
+ }
192
+ return Object.freeze({ toolName, claim });
193
+ }
194
+
139
195
  function emptyActionEvidenceReceipt(turnId) {
140
196
  const normalized = normalizedTurnId(turnId);
141
197
  return Object.freeze({
@@ -143,14 +199,17 @@ function emptyActionEvidenceReceipt(turnId) {
143
199
  completedTools: 0,
144
200
  successfulTools: 0,
145
201
  failedTools: 0,
202
+ successfulToolDigests: Object.freeze([]),
146
203
  digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
147
204
  });
148
205
  }
149
206
 
150
207
  function normalizeActionEvidenceReceipt(input) {
151
208
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
152
- const keys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
153
- if (!Object.keys(input).every((key) => keys.has(key)) || Object.keys(input).length !== keys.size) {
209
+ const requiredKeys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
210
+ const allowedKeys = new Set([...requiredKeys, 'successfulToolDigests']);
211
+ if (!Object.keys(input).every((key) => allowedKeys.has(key))
212
+ || ![...requiredKeys].every((key) => Object.hasOwn(input, key))) {
154
213
  throw new TypeError('evidence receipt fields are invalid');
155
214
  }
156
215
  const receipt = {
@@ -158,6 +217,7 @@ function normalizeActionEvidenceReceipt(input) {
158
217
  completedTools: Number(input.completedTools),
159
218
  successfulTools: Number(input.successfulTools),
160
219
  failedTools: Number(input.failedTools),
220
+ successfulToolDigests: normalizeSuccessfulToolDigests(input.successfulToolDigests),
161
221
  digest: String(input.digest ?? ''),
162
222
  };
163
223
  if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
@@ -188,6 +248,12 @@ function advanceActionEvidenceReceipt(current, input) {
188
248
  throw new TypeError('evidence input values are invalid');
189
249
  }
190
250
  const successful = decision === 'passed' && outcome === 'success';
251
+ const successfulToolDigests = [...prior.successfulToolDigests];
252
+ const toolDigest = successfulToolDigest(toolName);
253
+ if (successful && !successfulToolDigests.includes(toolDigest)) {
254
+ successfulToolDigests.push(toolDigest);
255
+ if (successfulToolDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolDigests.shift();
256
+ }
191
257
  const digest = crypto.createHash('sha256').update([
192
258
  prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
193
259
  ].join('\0')).digest('hex').slice(0, 16);
@@ -196,6 +262,7 @@ function advanceActionEvidenceReceipt(current, input) {
196
262
  completedTools: prior.completedTools + 1,
197
263
  successfulTools: prior.successfulTools + (successful ? 1 : 0),
198
264
  failedTools: prior.failedTools + (successful ? 0 : 1),
265
+ successfulToolDigests: Object.freeze(successfulToolDigests),
199
266
  digest,
200
267
  });
201
268
  }
@@ -281,6 +348,12 @@ function normalizeActionCheckpoint(input, options = {}) {
281
348
  ? normalizeActionEvidenceReceipt(input.evidenceReceipt)
282
349
  : undefined;
283
350
  if (evidenceReceipt !== undefined) checkpoint.evidenceReceipt = evidenceReceipt;
351
+ if (input.verificationProof !== undefined) {
352
+ if (phase !== 'verify' || evidenceBasis !== 'runtime_tool') {
353
+ throw new TypeError('verificationProof requires a runtime_tool verify checkpoint');
354
+ }
355
+ checkpoint.verificationProof = normalizeVerificationProof(input.verificationProof, evidenceReceipt);
356
+ }
284
357
  return Object.freeze(checkpoint);
285
358
  }
286
359
 
@@ -297,6 +370,9 @@ function projectActionCheckpoint(checkpoint) {
297
370
  ];
298
371
  lines.push(`Next action: ${value.nextAction}`);
299
372
  lines.push(`Expected evidence: ${value.expectedEvidence}`);
373
+ if (value.verificationProof !== undefined) {
374
+ lines.push(`Verification proof: ${value.verificationProof.toolName} - ${value.verificationProof.claim}`);
375
+ }
300
376
  if (value.nextTrigger !== undefined) {
301
377
  lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
302
378
  if (value.nextTrigger.dueAt !== undefined) lines.push(`Due at: ${value.nextTrigger.dueAt}`);
@@ -312,6 +388,7 @@ function projectActionCheckpoint(checkpoint) {
312
388
  lines.push(`Support choice: ${frame.supportChoice}`);
313
389
  lines.push(`Risk: ${frame.risk}`);
314
390
  lines.push(`Reversibility: ${frame.reversibility}`);
391
+ if (frame.decisionBasis !== undefined) lines.push(`Decision basis: ${frame.decisionBasis.join(' | ')}`);
315
392
  }
316
393
  if (value.evidenceReceipt !== undefined) {
317
394
  const receipt = value.evidenceReceipt;
@@ -331,5 +408,7 @@ module.exports = {
331
408
  assertActionCheckpointRevision,
332
409
  emptyActionEvidenceReceipt,
333
410
  normalizeActionCheckpoint,
411
+ normalizeVerificationProof,
334
412
  projectActionCheckpoint,
413
+ successfulToolDigest,
335
414
  };
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const crypto = require('node:crypto');
3
4
  const { resolveCognitiveEvidenceGroup } = require('./cognitive-effective-view.cjs');
4
5
  const {
5
6
  buildCognitiveSalienceIndex,
@@ -55,15 +56,19 @@ function normalizeScopes(value) {
55
56
  return scopes.every(Boolean) ? [...new Set(scopes)] : null;
56
57
  }
57
58
 
58
- function buildCognitiveFocusSelection(state, {
59
- focusScopes, maxItems = 6, maxChars = 900, currentSessionId,
60
- } = {}) {
61
- const scopes = normalizeScopes(focusScopes);
62
- if (scopes === null || !Number.isSafeInteger(maxItems) || maxItems < 1 || maxItems > 8
63
- || !Number.isSafeInteger(maxChars) || maxChars < 320 || maxChars > 1600
64
- || !Array.isArray(state?.observations)) return null;
65
- if (scopes.length === 0) return null;
59
+ function cognitiveEvidenceRef(item) {
60
+ const scope = clean(item?.scope, 128);
61
+ const domain = String(item?.domain ?? '');
62
+ const key = clean(item?.key, 128);
63
+ const observationId = clean(item?.observationId ?? item?.observation_id, 128);
64
+ if (!scope || scope === 'runtime' || !FOCUS_DOMAINS.has(domain) || !key || !observationId) return '';
65
+ return crypto.createHash('sha256')
66
+ .update([scope, domain, key, observationId].join('\0'))
67
+ .digest('hex').slice(0, 16);
68
+ }
66
69
 
70
+ function normalizedFocusGroups(state) {
71
+ if (!Array.isArray(state?.observations)) return null;
67
72
  const groups = new Map();
68
73
  state.observations.forEach((item, index) => {
69
74
  const domain = String(item?.domain ?? '');
@@ -80,7 +85,7 @@ function buildCognitiveFocusSelection(state, {
80
85
  ? 'legacy_unknown' : String(item.epistemic_state);
81
86
  const occurredAt = Date.parse(String(item?.occurred_at ?? ''));
82
87
  if (!FOCUS_DOMAINS.has(domain) || !key || !value || !scope || scope === 'runtime'
83
- || !Number.isFinite(confidence) || confidence < 0.5 || confidence > 1
88
+ || !observationId || !Number.isFinite(confidence) || confidence < 0.5 || confidence > 1
84
89
  || !EPISTEMIC_MARKERS.has(epistemicState)
85
90
  || !Number.isFinite(occurredAt)) return;
86
91
  const groupKey = `${scope}\0${domain}\0${key}`;
@@ -91,11 +96,66 @@ function buildCognitiveFocusSelection(state, {
91
96
  });
92
97
  groups.set(groupKey, existing);
93
98
  });
99
+ for (const entries of groups.values()) {
100
+ entries.sort((left, right) => left.occurredAt - right.occurredAt || left.index - right.index);
101
+ }
102
+ return groups;
103
+ }
104
+
105
+ function currentEvidenceRefs(entries, effective) {
106
+ if (!effective || effective.conflict) return [];
107
+ const withdrawals = entries.filter((item) => Boolean(item.withdraws));
108
+ const deletionCutoff = withdrawals.length > 0
109
+ ? Math.max(...withdrawals.map((item) => item.occurredAt)) : Number.NEGATIVE_INFINITY;
110
+ const evidence = entries.filter((item) => !item.withdraws && item.occurredAt > deletionCutoff);
111
+ const superseded = new Set(evidence.map((item) => item.supersedes).filter(Boolean));
112
+ return evidence
113
+ .filter((item) => !superseded.has(item.observationId) && item.value === effective.value)
114
+ .map(cognitiveEvidenceRef)
115
+ .filter(Boolean);
116
+ }
117
+
118
+ function evaluateCognitiveDecisionBasis(state, refs) {
119
+ if (refs === undefined || refs === null) return null;
120
+ if (!Array.isArray(refs) || refs.length < 1 || refs.length > 5
121
+ || refs.some((ref) => !/^[a-f0-9]{16}$/u.test(String(ref)))
122
+ || new Set(refs).size !== refs.length) {
123
+ return { status: 'unknown', currentRefs: [], staleRefs: [], unknownRefs: [] };
124
+ }
125
+ const groups = normalizedFocusGroups(state);
126
+ if (groups === null) return { status: 'unknown', currentRefs: [], staleRefs: [], unknownRefs: [...refs] };
127
+ const historical = new Set();
128
+ const current = new Set();
129
+ for (const entries of groups.values()) {
130
+ entries.map(cognitiveEvidenceRef).filter(Boolean).forEach((ref) => historical.add(ref));
131
+ const effective = resolveCognitiveEvidenceGroup(entries);
132
+ currentEvidenceRefs(entries, effective).forEach((ref) => current.add(ref));
133
+ }
134
+ const currentRefs = refs.filter((ref) => current.has(ref));
135
+ const staleRefs = refs.filter((ref) => !current.has(ref) && historical.has(ref));
136
+ const unknownRefs = refs.filter((ref) => !current.has(ref) && !historical.has(ref));
137
+ return {
138
+ status: staleRefs.length > 0 ? 'stale' : unknownRefs.length > 0 ? 'unknown' : 'current',
139
+ currentRefs,
140
+ staleRefs,
141
+ unknownRefs,
142
+ };
143
+ }
144
+
145
+ function buildCognitiveFocusSelection(state, {
146
+ focusScopes, maxItems = 6, maxChars = 900, currentSessionId,
147
+ } = {}) {
148
+ const scopes = normalizeScopes(focusScopes);
149
+ if (scopes === null || !Number.isSafeInteger(maxItems) || maxItems < 1 || maxItems > 8
150
+ || !Number.isSafeInteger(maxChars) || maxChars < 320 || maxChars > 1600) return null;
151
+ if (scopes.length === 0) return null;
152
+
153
+ const groups = normalizedFocusGroups(state);
154
+ if (groups === null) return null;
94
155
 
95
156
  const salience = buildCognitiveSalienceIndex(state, { currentSessionId });
96
157
  const ranked = [];
97
158
  for (const entries of groups.values()) {
98
- entries.sort((left, right) => left.occurredAt - right.occurredAt || left.index - right.index);
99
159
  const latest = entries.at(-1);
100
160
  if (!scopes.includes(latest.scope)) continue;
101
161
  const effective = resolveCognitiveEvidenceGroup(entries);
@@ -117,7 +177,7 @@ function buildCognitiveFocusSelection(state, {
117
177
  if (item.conflict) return `- Conflict: ${item.key} has incompatible evidence; verify before relying on it.`;
118
178
  const marker = item.corrected ? ' [corrected]'
119
179
  : item.contested ? ' [contested; stronger evidence]' : item.revised ? ' [revised]' : '';
120
- return `- ${LABELS.get(item.domain)}: ${item.value}${EPISTEMIC_MARKERS.get(item.epistemicState)}${marker}`;
180
+ return `- ${LABELS.get(item.domain)}: ${item.value}${EPISTEMIC_MARKERS.get(item.epistemicState)}${marker} [ref:${cognitiveEvidenceRef(item)}]`;
121
181
  });
122
182
  while ([`Current durable focus (${lines.length} items):`, ...lines, SAFETY_LINE].join('\n').length > maxChars
123
183
  && lines.length > 0) lines.pop();
@@ -125,7 +185,12 @@ function buildCognitiveFocusSelection(state, {
125
185
  const kept = selected.slice(0, lines.length);
126
186
  return {
127
187
  text: [`Current durable focus (${lines.length} items):`, ...lines, SAFETY_LINE].join('\n'),
128
- selected: kept.map((item) => ({ scope: item.scope, domain: item.domain, key: item.key })),
188
+ selected: kept.map((item) => ({
189
+ scope: item.scope,
190
+ domain: item.domain,
191
+ key: item.key,
192
+ ...(item.conflict ? {} : { evidenceRef: cognitiveEvidenceRef(item) }),
193
+ })),
129
194
  };
130
195
  }
131
196
 
@@ -133,4 +198,9 @@ function buildCognitiveFocusProjection(state, options) {
133
198
  return buildCognitiveFocusSelection(state, options)?.text ?? null;
134
199
  }
135
200
 
136
- module.exports = { buildCognitiveFocusProjection, buildCognitiveFocusSelection };
201
+ module.exports = {
202
+ buildCognitiveFocusProjection,
203
+ buildCognitiveFocusSelection,
204
+ cognitiveEvidenceRef,
205
+ evaluateCognitiveDecisionBasis,
206
+ };
@@ -6,7 +6,10 @@ const path = require('node:path');
6
6
  const { openCognitiveMemoryAdapter } = require('./cognitive-memory-adapter.cjs');
7
7
  const { loadConfiguredCognitiveMemoryAdapter } = require('./cognitive-memory-provider.cjs');
8
8
  const { buildCognitiveContextProjection } = require('./cognitive-context-projection.cjs');
9
- const { buildCognitiveFocusSelection } = require('./cognitive-focus-projection.cjs');
9
+ const {
10
+ buildCognitiveFocusSelection,
11
+ evaluateCognitiveDecisionBasis,
12
+ } = require('./cognitive-focus-projection.cjs');
10
13
  const { cognitiveEntityKey, focusAccessObservation } = require('./cognitive-salience-policy.cjs');
11
14
  const { authorizeAttentionCandidate } = require('./cognitive-attention-runtime.cjs');
12
15
 
@@ -490,6 +493,11 @@ function createCognitiveTurnLifecycle({
490
493
  return [focus?.text, continuity].filter(Boolean).join('\n\n') || null;
491
494
  }
492
495
 
496
+ function evaluateDecisionBasis(refs) {
497
+ const state = store.read({ tenantId: tenant, agentId: agent }, internalMemoryAccess);
498
+ return evaluateCognitiveDecisionBasis(state, refs);
499
+ }
500
+
493
501
  function authorizeAttention(input) {
494
502
  const candidate = input?.candidate;
495
503
  const authorization = input?.authorization;
@@ -552,6 +560,7 @@ function createCognitiveTurnLifecycle({
552
560
  withdrawFocusObservation,
553
561
  authorizeAttention,
554
562
  memoryStatus,
563
+ evaluateDecisionBasis,
555
564
  projectForTurn,
556
565
  endTurn,
557
566
  read: () => store.read({ tenantId: tenant, agentId: agent }, internalMemoryAccess),
@@ -23,7 +23,7 @@ function bounded(value, max = 512) {
23
23
  return text.length <= max ? text : `${text.slice(0, max - 3).trimEnd()}...`;
24
24
  }
25
25
 
26
- function buildCognitiveWorkFocus(goal) {
26
+ function buildCognitiveWorkFocus(goal, { decisionFreshness } = {}) {
27
27
  if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return null;
28
28
  const goalId = String(goal.goalId ?? '').trim();
29
29
  const objective = bounded(goal.objective);
@@ -75,6 +75,11 @@ function buildCognitiveWorkFocus(goal) {
75
75
  const frameSupportChoice = bounded(problemFrame?.supportChoice, 256);
76
76
  const frameRisk = bounded(problemFrame?.risk);
77
77
  const frameReversibility = bounded(problemFrame?.reversibility);
78
+ const frameDecisionBasis = Array.isArray(problemFrame?.decisionBasis)
79
+ && problemFrame.decisionBasis.length >= 1 && problemFrame.decisionBasis.length <= 5
80
+ && problemFrame.decisionBasis.every((item) => /^[a-f0-9]{16}$/u.test(String(item)))
81
+ && new Set(problemFrame.decisionBasis).size === problemFrame.decisionBasis.length
82
+ ? [...problemFrame.decisionBasis] : [];
78
83
  const hasProblemFrame = problemFrame !== null && frameSuccessCriterion
79
84
  && frameMissingKnowledge.length > 0 && frameCandidates.length > 0
80
85
  && frameSelectedAction && frameSelectionReason && frameSupportChoice
@@ -83,8 +88,17 @@ function buildCognitiveWorkFocus(goal) {
83
88
  && nextAction && checkpointEvidence;
84
89
  const epistemicState = hasCheckpoint && EPISTEMIC_STATES.has(checkpointEpistemicState)
85
90
  ? checkpointEpistemicState : hasCheckpoint ? 'legacy_unknown' : 'verified';
86
- const knowledgeGapValue = hasProblemFrame ? frameMissingKnowledge.join(' | ') : 'none';
91
+ const decisionFreshnessStatus = frameDecisionBasis.length > 0
92
+ && ['current', 'stale', 'unknown'].includes(decisionFreshness?.status)
93
+ ? decisionFreshness.status : frameDecisionBasis.length > 0 ? 'unknown' : '';
94
+ const staleDecision = decisionFreshnessStatus === 'stale' || decisionFreshnessStatus === 'unknown';
95
+ const replanKnowledgeGap = 'Re-evaluate the selected action against current durable evidence.';
96
+ const replanTrigger = 'Re-evaluate the selected action against the current durable evidence.';
97
+ const replanEvidence = 'A replacement action is bound to current durable evidence refs.';
98
+ const knowledgeGapValue = staleDecision ? `${replanKnowledgeGap} ${frameMissingKnowledge.join(' | ')}`
99
+ : hasProblemFrame ? frameMissingKnowledge.join(' | ') : 'none';
87
100
  const decisionThread = hasProblemFrame ? [
101
+ ...(staleDecision ? [`Decision basis ${decisionFreshnessStatus}; replan before executing the selected action.`] : []),
88
102
  `Goal is ${status}`,
89
103
  `Phase ${checkpointPhase}`,
90
104
  `Last verified: ${bounded(lastVerified, 80)}`,
@@ -112,6 +126,8 @@ function buildCognitiveWorkFocus(goal) {
112
126
  hasProblemFrame ? frameSupportChoice : '',
113
127
  hasProblemFrame ? frameRisk : '',
114
128
  hasProblemFrame ? frameReversibility : '',
129
+ hasProblemFrame ? frameDecisionBasis.join('\0') : '',
130
+ decisionFreshnessStatus,
115
131
  ].join('\0'))
116
132
  .digest('hex').slice(0, 40);
117
133
  const keyRoot = `goal:${goalId}`;
@@ -128,24 +144,34 @@ function buildCognitiveWorkFocus(goal) {
128
144
  `Next action: ${nextAction}`,
129
145
  ].join(' ')
130
146
  : `Goal is ${status}.`),
131
- confidence: 1, epistemicState, scope: focusScope,
147
+ confidence: 1,
148
+ epistemicState: staleDecision ? decisionFreshnessStatus : epistemicState,
149
+ scope: focusScope,
132
150
  },
133
151
  {
134
152
  domain: 'knowledge_gap', key: `${keyRoot}:knowledge-gaps`,
135
153
  value: knowledgeGapValue,
136
154
  confidence: 1,
137
- epistemicState: hasProblemFrame ? 'unknown' : 'verified',
155
+ epistemicState: hasProblemFrame || staleDecision ? 'unknown' : 'verified',
138
156
  scope: focusScope,
139
157
  },
140
158
  {
141
159
  domain: 'next_trigger', key: `${keyRoot}:next`,
142
- value: status === 'active' && hasCheckpoint ? triggerValue : NEXT_TRIGGER.get(status),
143
- confidence: 1, epistemicState, scope: focusScope,
160
+ value: status === 'active' && hasCheckpoint
161
+ ? staleDecision ? replanTrigger : triggerValue
162
+ : NEXT_TRIGGER.get(status),
163
+ confidence: 1,
164
+ epistemicState: staleDecision ? decisionFreshnessStatus : epistemicState,
165
+ scope: focusScope,
144
166
  },
145
167
  {
146
168
  domain: 'expected_evidence', key: `${keyRoot}:evidence`,
147
- value: status === 'active' && hasCheckpoint ? checkpointEvidence : completionCriterion,
148
- confidence: 1, epistemicState, scope: focusScope,
169
+ value: status === 'active' && hasCheckpoint
170
+ ? staleDecision ? replanEvidence : checkpointEvidence
171
+ : completionCriterion,
172
+ confidence: 1,
173
+ epistemicState: staleDecision ? decisionFreshnessStatus : epistemicState,
174
+ scope: focusScope,
149
175
  },
150
176
  ],
151
177
  };
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ const {
4
+ normalizeVerificationProof,
5
+ } = require('./cognitive-action-checkpoint.cjs');
6
+
3
7
  function hasCompletionCriterion(goal) {
4
8
  return typeof goal?.completionCriterion === 'string'
5
9
  && goal.completionCriterion.trim().length > 0;
@@ -10,6 +14,24 @@ function successfulRuntimeEvidence(checkpoint) {
10
14
  return Number.isSafeInteger(successfulTools) && successfulTools > 0;
11
15
  }
12
16
 
17
+ function verificationProofGaps(checkpoint) {
18
+ if (!checkpoint?.verificationProof) {
19
+ return ['Bind the completion claim to a successful verification tool from the checkpoint turn.'];
20
+ }
21
+ try {
22
+ normalizeVerificationProof(checkpoint.verificationProof, checkpoint.evidenceReceipt);
23
+ return [];
24
+ } catch (error) {
25
+ if (/action-only tool/u.test(String(error?.message ?? ''))) {
26
+ return ['The completion proof names an action-only tool, not a verification tool.'];
27
+ }
28
+ if (/successful current-turn tool/u.test(String(error?.message ?? ''))) {
29
+ return ['The completion proof does not match a successful verification tool from the checkpoint turn.'];
30
+ }
31
+ return ['The completion verification proof is malformed.'];
32
+ }
33
+ }
34
+
13
35
  function evaluateGoalCompletionEvidence(goal) {
14
36
  if (!hasCompletionCriterion(goal)) {
15
37
  return {
@@ -40,9 +62,16 @@ function evaluateGoalCompletionEvidence(goal) {
40
62
  if (checkpoint.epistemicState !== 'verified') {
41
63
  gaps.push('The completion proof must be classified as verified.');
42
64
  }
43
- if (!successfulRuntimeEvidence(checkpoint)) {
65
+ const hasRuntimeEvidence = successfulRuntimeEvidence(checkpoint);
66
+ if (!hasRuntimeEvidence) {
44
67
  gaps.push('The runtime evidence receipt must contain at least one successful tool result.');
45
68
  }
69
+ if (checkpoint.phase === 'verify'
70
+ && checkpoint.evidenceBasis === 'runtime_tool'
71
+ && checkpoint.epistemicState === 'verified'
72
+ && hasRuntimeEvidence) {
73
+ gaps.push(...verificationProofGaps(checkpoint));
74
+ }
46
75
 
47
76
  return {
48
77
  required: true,
package/blun.mjs CHANGED
@@ -245806,7 +245806,8 @@ var init_events$1 = __esmMin((() => {
245806
245806
  selectionReason: string(),
245807
245807
  supportChoice: string(),
245808
245808
  risk: string(),
245809
- reversibility: string()
245809
+ reversibility: string(),
245810
+ decisionBasis: array(string()).optional()
245810
245811
  }).strict().optional(),
245811
245812
  updatedAt: string(),
245812
245813
  evidenceReceipt: object({
@@ -260306,6 +260307,7 @@ bytesWritten: number$1().int().nonnegative() });
260306
260307
  var create_goal_default;
260307
260308
  var init_create_goal$1 = __esmMin((() => {
260308
260309
  create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome,\n- an authenticated user assigns a non-trivial multi-step outcome with a checkable end state under an existing instruction to continue autonomously, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, one-step requests, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them. Start every created goal with revision 1 and a complete `problemFrame`\ninside `actionCheckpoint`, so the success criterion, missing knowledge, candidate actions, chosen\naction, support choice, risk, reversibility, next action, expected evidence, and exact next trigger survive interruption.\nThis frame is descriptive state only and never grants permission.\n\nCreating a goal fails if one already exists, so use `replace: true` only when the user explicitly\nwants to abandon the current goal and start a new one.\n";
260310
+ create_goal_default += "\nBind each selected action to the projected durable facts or assumptions it relies on by copying their explicit refs into `decisionBasis`. A stale or unknown decision basis requires replanning before execution.\n";
260309
260311
  }));
260310
260312
  //#endregion
260311
260313
  //#region ../../packages/agent-core/src/tools/builtin/goal/serialize.ts
@@ -260333,7 +260335,8 @@ function createProblemFrameInputSchema() {
260333
260335
  selectionReason: string().min(1).max(512),
260334
260336
  supportChoice: string().min(1).max(256),
260335
260337
  risk: string().min(1).max(512),
260336
- reversibility: string().min(1).max(512)
260338
+ reversibility: string().min(1).max(512),
260339
+ decisionBasis: array(string().regex(/^[a-f0-9]{16}$/u)).min(1).max(5).optional()
260337
260340
  }).strict();
260338
260341
  }
260339
260342
  function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFrame = false) {
@@ -260345,6 +260348,10 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
260345
260348
  lastVerified: string().min(1).max(512),
260346
260349
  nextAction: string().min(1).max(512),
260347
260350
  expectedEvidence: string().min(1).max(512),
260351
+ verificationProof: object({
260352
+ toolName: string().min(1).max(128),
260353
+ claim: string().min(1).max(512)
260354
+ }).strict().optional(),
260348
260355
  nextTrigger: object({
260349
260356
  kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
260350
260357
  condition: string().min(1).max(512),
@@ -261835,7 +261842,9 @@ var init_turn = __esmMin((() => {
261835
261842
  try {
261836
261843
  const focusScopes = cognitiveFocusScopesForTurn(input);
261837
261844
  const lifecycle = this.getCognitiveLifecycle();
261838
- const workFocus = buildCognitiveWorkFocus(this.agent.goal.getGoal().goal);
261845
+ const goal = this.agent.goal.getGoal().goal;
261846
+ const decisionFreshness = lifecycle?.evaluateDecisionBasis(goal?.actionCheckpoint?.problemFrame?.decisionBasis) ?? null;
261847
+ const workFocus = buildCognitiveWorkFocus(goal, { decisionFreshness });
261839
261848
  if (workFocus !== null) {
261840
261849
  lifecycle?.recordFocusSnapshot({
261841
261850
  snapshotId: workFocus.snapshotId,
@@ -262863,7 +262872,8 @@ var init_outcome_prompts = __esmMin((() => {}));
262863
262872
  var update_goal_default;
262864
262873
  var init_update_goal$1 = __esmMin((() => {
262865
262874
  update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. A `time` trigger must include the exact ISO timestamp in `dueAt`; no other trigger kind may include `dueAt`. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
262866
- update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Problem framing is descriptive state and never grants permission.\n";
262875
+ update_goal_default += "\nBefore completing a goal with a criterion, bind the verified claim to the successful current-turn verification tool in `verificationProof`. A write or edit is an action, not proof that the changed behavior works. Run a separate observation, test, or counterexample probe and name that successful tool plus the exact claim it supports.\n";
262876
+ update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Bind each selected action to the projected durable facts or assumptions it relies on by copying their explicit refs into `decisionBasis`. A stale or unknown decision basis requires replanning before execution. Problem framing is descriptive state and never grants permission.\n";
262867
262877
  }));
262868
262878
  //#endregion
262869
262879
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.446",
3
+ "version": "9.1.448",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {