blun-king-cli 9.1.447 → 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,7 +13,7 @@ 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([
@@ -27,6 +27,12 @@ const EVIDENCE_INPUT_KEYS = new Set([
27
27
  const EVIDENCE_DIGEST_RE = /^[a-f0-9]{16}$/u;
28
28
  const DECISION_BASIS_RE = /^[a-f0-9]{16}$/u;
29
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;
30
36
 
31
37
  function bounded(value, field, max = 512) {
32
38
  const text = String(value ?? '')
@@ -148,6 +154,44 @@ function normalizedTurnId(value) {
148
154
  return turnId;
149
155
  }
150
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
+
151
195
  function emptyActionEvidenceReceipt(turnId) {
152
196
  const normalized = normalizedTurnId(turnId);
153
197
  return Object.freeze({
@@ -155,14 +199,17 @@ function emptyActionEvidenceReceipt(turnId) {
155
199
  completedTools: 0,
156
200
  successfulTools: 0,
157
201
  failedTools: 0,
202
+ successfulToolDigests: Object.freeze([]),
158
203
  digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
159
204
  });
160
205
  }
161
206
 
162
207
  function normalizeActionEvidenceReceipt(input) {
163
208
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
164
- const keys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
165
- 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))) {
166
213
  throw new TypeError('evidence receipt fields are invalid');
167
214
  }
168
215
  const receipt = {
@@ -170,6 +217,7 @@ function normalizeActionEvidenceReceipt(input) {
170
217
  completedTools: Number(input.completedTools),
171
218
  successfulTools: Number(input.successfulTools),
172
219
  failedTools: Number(input.failedTools),
220
+ successfulToolDigests: normalizeSuccessfulToolDigests(input.successfulToolDigests),
173
221
  digest: String(input.digest ?? ''),
174
222
  };
175
223
  if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
@@ -200,6 +248,12 @@ function advanceActionEvidenceReceipt(current, input) {
200
248
  throw new TypeError('evidence input values are invalid');
201
249
  }
202
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
+ }
203
257
  const digest = crypto.createHash('sha256').update([
204
258
  prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
205
259
  ].join('\0')).digest('hex').slice(0, 16);
@@ -208,6 +262,7 @@ function advanceActionEvidenceReceipt(current, input) {
208
262
  completedTools: prior.completedTools + 1,
209
263
  successfulTools: prior.successfulTools + (successful ? 1 : 0),
210
264
  failedTools: prior.failedTools + (successful ? 0 : 1),
265
+ successfulToolDigests: Object.freeze(successfulToolDigests),
211
266
  digest,
212
267
  });
213
268
  }
@@ -293,6 +348,12 @@ function normalizeActionCheckpoint(input, options = {}) {
293
348
  ? normalizeActionEvidenceReceipt(input.evidenceReceipt)
294
349
  : undefined;
295
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
+ }
296
357
  return Object.freeze(checkpoint);
297
358
  }
298
359
 
@@ -309,6 +370,9 @@ function projectActionCheckpoint(checkpoint) {
309
370
  ];
310
371
  lines.push(`Next action: ${value.nextAction}`);
311
372
  lines.push(`Expected evidence: ${value.expectedEvidence}`);
373
+ if (value.verificationProof !== undefined) {
374
+ lines.push(`Verification proof: ${value.verificationProof.toolName} - ${value.verificationProof.claim}`);
375
+ }
312
376
  if (value.nextTrigger !== undefined) {
313
377
  lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
314
378
  if (value.nextTrigger.dueAt !== undefined) lines.push(`Due at: ${value.nextTrigger.dueAt}`);
@@ -344,5 +408,7 @@ module.exports = {
344
408
  assertActionCheckpointRevision,
345
409
  emptyActionEvidenceReceipt,
346
410
  normalizeActionCheckpoint,
411
+ normalizeVerificationProof,
347
412
  projectActionCheckpoint,
413
+ successfulToolDigest,
348
414
  };
@@ -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
@@ -260348,6 +260348,10 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
260348
260348
  lastVerified: string().min(1).max(512),
260349
260349
  nextAction: string().min(1).max(512),
260350
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(),
260351
260355
  nextTrigger: object({
260352
260356
  kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
260353
260357
  condition: string().min(1).max(512),
@@ -262868,6 +262872,7 @@ var init_outcome_prompts = __esmMin((() => {}));
262868
262872
  var update_goal_default;
262869
262873
  var init_update_goal$1 = __esmMin((() => {
262870
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";
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";
262871
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";
262872
262877
  }));
262873
262878
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.447",
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": {