blun-king-cli 9.1.450 → 9.1.452
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,6 +13,7 @@ const TRIGGER_KINDS = new Set([
|
|
|
13
13
|
'immediate', 'external_event', 'time', 'dependency', 'user_decision',
|
|
14
14
|
]);
|
|
15
15
|
const VERIFICATION_SUBJECTS = new Set(['result', 'verifier']);
|
|
16
|
+
const VERIFICATION_KINDS = new Set(['inspection', 'integrity', 'syntax', 'test', 'reachability']);
|
|
16
17
|
const MODEL_KEYS = new Set([
|
|
17
18
|
'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'verificationProof', 'nextTrigger', 'problemFrame', 'updatedAt',
|
|
18
19
|
]);
|
|
@@ -24,6 +25,7 @@ const PROBLEM_FRAME_KEYS = new Set([
|
|
|
24
25
|
const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
|
|
25
26
|
const EVIDENCE_INPUT_KEYS = new Set([
|
|
26
27
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs', 'toolArgs',
|
|
28
|
+
'resultEvidenceKinds', 'resultEvidenceScopes',
|
|
27
29
|
]);
|
|
28
30
|
const REQUIRED_EVIDENCE_INPUT_KEYS = new Set([
|
|
29
31
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
|
|
@@ -36,14 +38,20 @@ const ACTION_ONLY_TOOL_NAMES = new Set([
|
|
|
36
38
|
'ExitPlanMode', 'GenerateImage', 'GenerateSpeech', 'GenerateVideo', 'LipSyncMedia',
|
|
37
39
|
'MistakeRecord', 'SetGoalBudget', 'TaskStop', 'TaskUpdate', 'UpdateGoal', 'Write',
|
|
38
40
|
]);
|
|
39
|
-
const
|
|
41
|
+
const INSPECTION_TOOL_NAMES = new Set([
|
|
40
42
|
'codebasesearch', 'cronlist', 'fetchurl', 'getgoal', 'getmedia', 'glob', 'grep',
|
|
41
|
-
'read', 'readmediafile', 'taskoutput', '
|
|
43
|
+
'read', 'readmediafile', 'taskoutput', 'understandimage', 'understandvideo',
|
|
42
44
|
'websearch',
|
|
43
45
|
]);
|
|
46
|
+
const TEST_TOOL_NAMES = new Set(['test']);
|
|
44
47
|
const COMMAND_TOOL_NAMES = new Set(['bash', 'command', 'exec_command', 'shell']);
|
|
45
48
|
const MUTATING_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:cp|mv|rm|mkdir|touch|tee|install|scp|sftp|ssh)\b|(?:sed\s+-i\b)|(?:git\s+(?:add|commit|push|checkout|switch|reset|clean|merge|rebase)\b)|(?:Set-Content|Add-Content|Copy-Item|Move-Item|Remove-Item|New-Item|Start-Process)\b)/iu;
|
|
46
|
-
const
|
|
49
|
+
const INSPECTION_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:git\s+(?:status|rev-parse|show))|(?:wc|rg|grep|cat|ls|stat)\b|(?:Get-Content|Get-Item|Test-Path|Measure-Object)\b)/iu;
|
|
50
|
+
const INTEGRITY_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:git\s+(?:diff(?:\s+--check)?|fsck))|(?:sha(?:1|256|512)sum|shasum|cmp|diff)\b|(?:certutil\s+-hashfile)|(?:Get-FileHash|Compare-Object)\b)/iu;
|
|
51
|
+
const SYNTAX_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--check\b)|(?:(?:npx\s+)?tsc\b)|(?:(?:npm|pnpm|yarn)\s+(?:(?:run\s+)?typecheck)\b))/iu;
|
|
52
|
+
const TEST_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--test\b)|(?:node\s+(?!--)[^\s;&|]*(?:check|test|verify|validate|lint|gate|probe)[^\s;&|]*\b)|(?:(?:npm|pnpm|yarn)\s+(?:test|(?:run\s+)?(?:test|lint|check|build))\b)|(?:python(?:3)?\s+-m\s+pytest\b)|(?:pytest\b)|(?:go\s+test\b)|(?:cargo\s+test\b)|(?:dotnet\s+test\b)|(?:(?:npx\s+)?(?:eslint|biome\s+check)\b))/iu;
|
|
53
|
+
const RUNTIME_EVIDENCE_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:node\s+(?!--check\b)(?:-e\b|[^\s;&|]+)|(?:python(?:3)?\s+(?!-m\s+pytest\b)[^\s;&|]+)|(?:npm|pnpm|yarn)\s+(?:test|run\b)|pytest\b|go\s+test\b|cargo\s+test\b|dotnet\s+test\b)/iu;
|
|
54
|
+
const EVIDENCE_SCOPE_MARKER = /(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu;
|
|
47
55
|
const MAX_SUCCESSFUL_TOOL_DIGESTS = 32;
|
|
48
56
|
|
|
49
57
|
function bounded(value, field, max = 512) {
|
|
@@ -181,6 +189,33 @@ function successfulToolCallDigest(turnId, toolCallId, toolName) {
|
|
|
181
189
|
.slice(0, 16);
|
|
182
190
|
}
|
|
183
191
|
|
|
192
|
+
function successfulVerificationCallDigest(turnId, toolCallId, toolName, kind) {
|
|
193
|
+
const turn = normalizedTurnId(turnId);
|
|
194
|
+
const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
|
|
195
|
+
const name = bounded(toolName, 'verificationProof toolName', 128);
|
|
196
|
+
const normalizedKind = normalizedVerificationKind(kind);
|
|
197
|
+
return crypto.createHash('sha256')
|
|
198
|
+
.update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}`)
|
|
199
|
+
.digest('hex')
|
|
200
|
+
.slice(0, 16);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizedVerificationScope(value, field = 'verificationProof scope') {
|
|
204
|
+
return bounded(value, field, 256);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function successfulVerificationScopeDigest(turnId, toolCallId, toolName, kind, scope) {
|
|
208
|
+
const turn = normalizedTurnId(turnId);
|
|
209
|
+
const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
|
|
210
|
+
const name = bounded(toolName, 'verificationProof toolName', 128);
|
|
211
|
+
const normalizedKind = normalizedVerificationKind(kind);
|
|
212
|
+
const normalizedScope = normalizedVerificationScope(scope);
|
|
213
|
+
return crypto.createHash('sha256')
|
|
214
|
+
.update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}\0scope:${normalizedScope}`)
|
|
215
|
+
.digest('hex')
|
|
216
|
+
.slice(0, 16);
|
|
217
|
+
}
|
|
218
|
+
|
|
184
219
|
function isActionOnlyTool(toolName) {
|
|
185
220
|
return ACTION_ONLY_TOOL_NAMES.has(String(toolName ?? '').trim());
|
|
186
221
|
}
|
|
@@ -191,14 +226,93 @@ function verificationCommand(toolArgs) {
|
|
|
191
226
|
return typeof command === 'string' ? command.trim().replace(/\s+/gu, ' ') : '';
|
|
192
227
|
}
|
|
193
228
|
|
|
194
|
-
function
|
|
229
|
+
function toolArgumentScope(toolName, toolArgs) {
|
|
230
|
+
if (!toolArgs || typeof toolArgs !== 'object' || Array.isArray(toolArgs)) return '';
|
|
231
|
+
const name = String(toolName ?? '').trim().toLowerCase();
|
|
232
|
+
const first = (...keys) => {
|
|
233
|
+
for (const key of keys) {
|
|
234
|
+
if (typeof toolArgs[key] === 'string' && toolArgs[key].trim().length > 0) {
|
|
235
|
+
return normalizedVerificationScope(toolArgs[key], 'verification target scope');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return '';
|
|
239
|
+
};
|
|
240
|
+
if (['read', 'readmediafile', 'understandimage', 'understandvideo'].includes(name)) {
|
|
241
|
+
return first('file_path', 'path', 'image_path', 'video_path');
|
|
242
|
+
}
|
|
243
|
+
if (name === 'getmedia') return first('media_id', 'id', 'path');
|
|
244
|
+
if (name === 'taskoutput') return first('task_id', 'id');
|
|
245
|
+
if (name === 'fetchurl') return first('url', 'uri');
|
|
246
|
+
if (name === 'websearch') return first('query', 'q');
|
|
247
|
+
if (name === 'grep' || name === 'glob' || name === 'codebasesearch') {
|
|
248
|
+
return first('path', 'cwd', 'directory', 'root', 'query', 'pattern');
|
|
249
|
+
}
|
|
250
|
+
if (name === 'getgoal') return 'runtime:active-goal';
|
|
251
|
+
if (name === 'cronlist') return 'runtime:cron-jobs';
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function commandEvidenceScopes(toolName, toolArgs, providedScopes) {
|
|
256
|
+
const normalizedName = String(toolName ?? '').trim().toLowerCase();
|
|
257
|
+
if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
|
|
258
|
+
const command = verificationCommand(toolArgs);
|
|
259
|
+
if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
|
|
260
|
+
const declared = new Set();
|
|
261
|
+
EVIDENCE_SCOPE_MARKER.lastIndex = 0;
|
|
262
|
+
for (const match of command.matchAll(EVIDENCE_SCOPE_MARKER)) declared.add(match[1]);
|
|
263
|
+
return Object.freeze(providedScopes.filter((scope) => declared.has(scope)));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function verificationKindsForToolCall(toolName, toolArgs) {
|
|
267
|
+
const normalizedName = String(toolName ?? '').trim().toLowerCase();
|
|
268
|
+
if (INSPECTION_TOOL_NAMES.has(normalizedName)) return Object.freeze(['inspection']);
|
|
269
|
+
if (TEST_TOOL_NAMES.has(normalizedName)) return Object.freeze(['test']);
|
|
270
|
+
if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
|
|
271
|
+
const command = verificationCommand(toolArgs);
|
|
272
|
+
if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
|
|
273
|
+
const kinds = [];
|
|
274
|
+
if (INSPECTION_COMMAND.test(command)) kinds.push('inspection');
|
|
275
|
+
if (INTEGRITY_COMMAND.test(command)) kinds.push('integrity');
|
|
276
|
+
if (SYNTAX_COMMAND.test(command)) kinds.push('syntax');
|
|
277
|
+
if (TEST_COMMAND.test(command)) kinds.push('test');
|
|
278
|
+
return Object.freeze(kinds);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function canCarryExplicitResultEvidence(toolName, toolArgs) {
|
|
195
282
|
const normalizedName = String(toolName ?? '').trim().toLowerCase();
|
|
196
|
-
if (VERIFICATION_TOOL_NAMES.has(normalizedName)) return true;
|
|
197
283
|
if (!COMMAND_TOOL_NAMES.has(normalizedName)) return false;
|
|
198
284
|
const command = verificationCommand(toolArgs);
|
|
199
285
|
return command.length > 0
|
|
200
286
|
&& !MUTATING_COMMAND.test(command)
|
|
201
|
-
&&
|
|
287
|
+
&& RUNTIME_EVIDENCE_COMMAND.test(command);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizedVerificationKind(value, field = 'verificationProof kind') {
|
|
291
|
+
const kind = String(value ?? '').trim();
|
|
292
|
+
if (!VERIFICATION_KINDS.has(kind)) throw new TypeError(`${field} is invalid`);
|
|
293
|
+
return kind;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function normalizedResultEvidenceKinds(value) {
|
|
297
|
+
if (value === undefined) return Object.freeze([]);
|
|
298
|
+
const kinds = Array.isArray(value) ? value.map((item) => String(item ?? '').trim()) : [];
|
|
299
|
+
if (!Array.isArray(value) || value.length > VERIFICATION_KINDS.size
|
|
300
|
+
|| kinds.some((item) => !VERIFICATION_KINDS.has(item))
|
|
301
|
+
|| new Set(kinds).size !== kinds.length) {
|
|
302
|
+
throw new TypeError('result evidence kinds are invalid');
|
|
303
|
+
}
|
|
304
|
+
return Object.freeze(kinds);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizedResultEvidenceScopes(value) {
|
|
308
|
+
if (value === undefined) return Object.freeze([]);
|
|
309
|
+
const scopes = Array.isArray(value)
|
|
310
|
+
? value.map((item, index) => normalizedVerificationScope(item, `resultEvidenceScopes[${index}]`))
|
|
311
|
+
: [];
|
|
312
|
+
if (!Array.isArray(value) || value.length > 5 || new Set(scopes).size !== scopes.length) {
|
|
313
|
+
throw new TypeError('result evidence scopes are invalid');
|
|
314
|
+
}
|
|
315
|
+
return Object.freeze(scopes);
|
|
202
316
|
}
|
|
203
317
|
|
|
204
318
|
function normalizeSuccessfulToolDigests(value) {
|
|
@@ -211,12 +325,17 @@ function normalizeSuccessfulToolDigests(value) {
|
|
|
211
325
|
return Object.freeze([...value]);
|
|
212
326
|
}
|
|
213
327
|
|
|
214
|
-
function normalizeExactVerificationCall(input, receipt, label) {
|
|
328
|
+
function normalizeExactVerificationCall(input, receipt, label, options = {}) {
|
|
329
|
+
const requireKind = options.requireKind === true;
|
|
330
|
+
const requireScope = options.requireScope === true;
|
|
331
|
+
const expectedKeys = 3 + (requireKind ? 1 : 0) + (requireScope ? 1 : 0);
|
|
215
332
|
if (!input || typeof input !== 'object' || Array.isArray(input)
|
|
216
|
-
|| Object.keys(input).length !==
|
|
333
|
+
|| Object.keys(input).length !== expectedKeys
|
|
217
334
|
|| !Object.hasOwn(input, 'toolCallId')
|
|
218
335
|
|| !Object.hasOwn(input, 'toolName')
|
|
219
|
-
|| !Object.hasOwn(input, 'claim')
|
|
336
|
+
|| !Object.hasOwn(input, 'claim')
|
|
337
|
+
|| requireKind !== Object.hasOwn(input, 'kind')
|
|
338
|
+
|| requireScope !== Object.hasOwn(input, 'scope')) {
|
|
220
339
|
throw new TypeError(`${label} fields are invalid`);
|
|
221
340
|
}
|
|
222
341
|
const toolCallId = bounded(input.toolCallId, `${label} toolCallId`, 256);
|
|
@@ -230,6 +349,24 @@ function normalizeExactVerificationCall(input, receipt, label) {
|
|
|
230
349
|
if (!receipt.successfulToolCallDigests.includes(callDigest)) {
|
|
231
350
|
throw new TypeError(`${label} must match a successful current-turn verification call`);
|
|
232
351
|
}
|
|
352
|
+
if (requireKind) {
|
|
353
|
+
const kind = normalizedVerificationKind(input.kind, `${label} kind`);
|
|
354
|
+
const typedDigest = successfulVerificationCallDigest(receipt.turnId, toolCallId, toolName, kind);
|
|
355
|
+
if (!receipt.successfulVerificationCallDigests.includes(typedDigest)) {
|
|
356
|
+
throw new TypeError(`${label} kind must match the successful current-turn verification call`);
|
|
357
|
+
}
|
|
358
|
+
if (requireScope) {
|
|
359
|
+
const scope = normalizedVerificationScope(input.scope, `${label} scope`);
|
|
360
|
+
const scopeDigest = successfulVerificationScopeDigest(
|
|
361
|
+
receipt.turnId, toolCallId, toolName, kind, scope,
|
|
362
|
+
);
|
|
363
|
+
if (!receipt.successfulVerificationScopeDigests.includes(scopeDigest)) {
|
|
364
|
+
throw new TypeError(`${label} scope must match the successful current-turn verification call target`);
|
|
365
|
+
}
|
|
366
|
+
return Object.freeze({ toolCallId, toolName, kind, scope, claim });
|
|
367
|
+
}
|
|
368
|
+
return Object.freeze({ toolCallId, toolName, kind, claim });
|
|
369
|
+
}
|
|
233
370
|
return Object.freeze({ toolCallId, toolName, claim });
|
|
234
371
|
}
|
|
235
372
|
|
|
@@ -247,18 +384,52 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
|
|
|
247
384
|
&& Object.hasOwn(input, 'toolName')
|
|
248
385
|
&& Object.hasOwn(input, 'claim');
|
|
249
386
|
const subject = String(input?.subject ?? '').trim();
|
|
250
|
-
const
|
|
387
|
+
const legacySubjectResult = options.allowLegacy === true
|
|
388
|
+
&& subject === 'result'
|
|
251
389
|
&& keys.length === 4
|
|
252
390
|
&& Object.hasOwn(input, 'toolCallId')
|
|
253
391
|
&& Object.hasOwn(input, 'toolName')
|
|
254
392
|
&& Object.hasOwn(input, 'claim');
|
|
255
|
-
const
|
|
393
|
+
const legacySubjectVerifier = options.allowLegacy === true
|
|
394
|
+
&& subject === 'verifier'
|
|
256
395
|
&& (keys.length === 4 || keys.length === 5)
|
|
257
396
|
&& Object.hasOwn(input, 'toolCallId')
|
|
258
397
|
&& Object.hasOwn(input, 'toolName')
|
|
259
398
|
&& Object.hasOwn(input, 'claim')
|
|
260
399
|
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'claim', 'sharpnessProof'].includes(key));
|
|
261
|
-
|
|
400
|
+
const legacyTypedSubjectResult = options.allowLegacy === true
|
|
401
|
+
&& subject === 'result'
|
|
402
|
+
&& keys.length === 5
|
|
403
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
404
|
+
&& Object.hasOwn(input, 'toolName')
|
|
405
|
+
&& Object.hasOwn(input, 'kind')
|
|
406
|
+
&& Object.hasOwn(input, 'claim');
|
|
407
|
+
const legacyTypedSubjectVerifier = options.allowLegacy === true
|
|
408
|
+
&& subject === 'verifier'
|
|
409
|
+
&& (keys.length === 5 || keys.length === 6)
|
|
410
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
411
|
+
&& Object.hasOwn(input, 'toolName')
|
|
412
|
+
&& Object.hasOwn(input, 'kind')
|
|
413
|
+
&& Object.hasOwn(input, 'claim')
|
|
414
|
+
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'claim', 'sharpnessProof'].includes(key));
|
|
415
|
+
const currentResult = subject === 'result'
|
|
416
|
+
&& keys.length === 6
|
|
417
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
418
|
+
&& Object.hasOwn(input, 'toolName')
|
|
419
|
+
&& Object.hasOwn(input, 'kind')
|
|
420
|
+
&& Object.hasOwn(input, 'scope')
|
|
421
|
+
&& Object.hasOwn(input, 'claim');
|
|
422
|
+
const currentVerifier = subject === 'verifier'
|
|
423
|
+
&& (keys.length === 6 || keys.length === 7)
|
|
424
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
425
|
+
&& Object.hasOwn(input, 'toolName')
|
|
426
|
+
&& Object.hasOwn(input, 'kind')
|
|
427
|
+
&& Object.hasOwn(input, 'scope')
|
|
428
|
+
&& Object.hasOwn(input, 'claim')
|
|
429
|
+
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'scope', 'claim', 'sharpnessProof'].includes(key));
|
|
430
|
+
if (!legacyName && !legacyExact && !legacySubjectResult && !legacySubjectVerifier
|
|
431
|
+
&& !legacyTypedSubjectResult && !legacyTypedSubjectVerifier
|
|
432
|
+
&& !currentResult && !currentVerifier) {
|
|
262
433
|
throw new TypeError('verificationProof fields are invalid');
|
|
263
434
|
}
|
|
264
435
|
const receipt = normalizeActionEvidenceReceipt(evidenceReceipt);
|
|
@@ -273,18 +444,28 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
|
|
|
273
444
|
}
|
|
274
445
|
return Object.freeze({ toolName, claim });
|
|
275
446
|
}
|
|
447
|
+
const legacySubject = legacySubjectResult || legacySubjectVerifier;
|
|
448
|
+
const legacyTypedSubject = legacyTypedSubjectResult || legacyTypedSubjectVerifier;
|
|
276
449
|
const primary = normalizeExactVerificationCall(legacyExact ? input : {
|
|
277
450
|
toolCallId: input.toolCallId,
|
|
278
451
|
toolName: input.toolName,
|
|
452
|
+
...(legacySubject ? {} : { kind: input.kind }),
|
|
453
|
+
...(currentResult || currentVerifier ? { scope: input.scope } : {}),
|
|
279
454
|
claim: input.claim,
|
|
280
|
-
}, receipt, 'verificationProof'
|
|
455
|
+
}, receipt, 'verificationProof', {
|
|
456
|
+
requireKind: !legacyExact && !legacySubject,
|
|
457
|
+
requireScope: currentResult || currentVerifier,
|
|
458
|
+
});
|
|
281
459
|
if (legacyExact) return primary;
|
|
282
460
|
if (!VERIFICATION_SUBJECTS.has(subject)) throw new TypeError('verificationProof subject is invalid');
|
|
283
461
|
if (subject === 'result') return Object.freeze({ subject, ...primary });
|
|
284
462
|
if (input.sharpnessProof === undefined) {
|
|
285
463
|
throw new TypeError('verifier proof requires a sharpnessProof');
|
|
286
464
|
}
|
|
287
|
-
const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof'
|
|
465
|
+
const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof', {
|
|
466
|
+
requireKind: !legacySubject,
|
|
467
|
+
requireScope: !legacySubject && !legacyTypedSubject,
|
|
468
|
+
});
|
|
288
469
|
if (sharpnessProof.toolCallId === primary.toolCallId
|
|
289
470
|
&& sharpnessProof.toolName === primary.toolName) {
|
|
290
471
|
throw new TypeError('sharpnessProof must name a distinct verification call');
|
|
@@ -301,6 +482,8 @@ function emptyActionEvidenceReceipt(turnId) {
|
|
|
301
482
|
failedTools: 0,
|
|
302
483
|
successfulToolDigests: Object.freeze([]),
|
|
303
484
|
successfulToolCallDigests: Object.freeze([]),
|
|
485
|
+
successfulVerificationCallDigests: Object.freeze([]),
|
|
486
|
+
successfulVerificationScopeDigests: Object.freeze([]),
|
|
304
487
|
digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
|
|
305
488
|
});
|
|
306
489
|
}
|
|
@@ -308,7 +491,10 @@ function emptyActionEvidenceReceipt(turnId) {
|
|
|
308
491
|
function normalizeActionEvidenceReceipt(input) {
|
|
309
492
|
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
|
|
310
493
|
const requiredKeys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
|
|
311
|
-
const allowedKeys = new Set([
|
|
494
|
+
const allowedKeys = new Set([
|
|
495
|
+
...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests',
|
|
496
|
+
'successfulVerificationCallDigests', 'successfulVerificationScopeDigests',
|
|
497
|
+
]);
|
|
312
498
|
if (!Object.keys(input).every((key) => allowedKeys.has(key))
|
|
313
499
|
|| ![...requiredKeys].every((key) => Object.hasOwn(input, key))) {
|
|
314
500
|
throw new TypeError('evidence receipt fields are invalid');
|
|
@@ -320,6 +506,12 @@ function normalizeActionEvidenceReceipt(input) {
|
|
|
320
506
|
failedTools: Number(input.failedTools),
|
|
321
507
|
successfulToolDigests: normalizeSuccessfulToolDigests(input.successfulToolDigests),
|
|
322
508
|
successfulToolCallDigests: normalizeSuccessfulToolDigests(input.successfulToolCallDigests),
|
|
509
|
+
successfulVerificationCallDigests: normalizeSuccessfulToolDigests(
|
|
510
|
+
input.successfulVerificationCallDigests,
|
|
511
|
+
),
|
|
512
|
+
successfulVerificationScopeDigests: normalizeSuccessfulToolDigests(
|
|
513
|
+
input.successfulVerificationScopeDigests,
|
|
514
|
+
),
|
|
323
515
|
digest: String(input.digest ?? ''),
|
|
324
516
|
};
|
|
325
517
|
if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
|
|
@@ -352,20 +544,58 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
352
544
|
const successful = decision === 'passed' && outcome === 'success';
|
|
353
545
|
const successfulToolDigests = [...prior.successfulToolDigests];
|
|
354
546
|
const successfulToolCallDigests = [...prior.successfulToolCallDigests];
|
|
547
|
+
const successfulVerificationCallDigests = [...prior.successfulVerificationCallDigests];
|
|
548
|
+
const successfulVerificationScopeDigests = [...prior.successfulVerificationScopeDigests];
|
|
355
549
|
const toolDigest = successfulToolDigest(toolName);
|
|
356
550
|
if (successful && !successfulToolDigests.includes(toolDigest)) {
|
|
357
551
|
successfulToolDigests.push(toolDigest);
|
|
358
552
|
if (successfulToolDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolDigests.shift();
|
|
359
553
|
}
|
|
360
|
-
const
|
|
554
|
+
const providedResultKinds = normalizedResultEvidenceKinds(input.resultEvidenceKinds);
|
|
555
|
+
const explicitResultKinds = successful && canCarryExplicitResultEvidence(toolName, input.toolArgs)
|
|
556
|
+
? providedResultKinds
|
|
557
|
+
: [];
|
|
558
|
+
const verificationKinds = successful
|
|
559
|
+
? [...new Set([...verificationKindsForToolCall(toolName, input.toolArgs), ...explicitResultKinds])]
|
|
560
|
+
: [];
|
|
561
|
+
const argumentScope = successful ? toolArgumentScope(toolName, input.toolArgs) : '';
|
|
562
|
+
const providedResultScopes = normalizedResultEvidenceScopes(input.resultEvidenceScopes);
|
|
563
|
+
const explicitResultScopes = successful
|
|
564
|
+
? commandEvidenceScopes(toolName, input.toolArgs, providedResultScopes)
|
|
565
|
+
: [];
|
|
566
|
+
const verificationScopes = [...new Set([
|
|
567
|
+
...(argumentScope ? [argumentScope] : []),
|
|
568
|
+
...explicitResultScopes,
|
|
569
|
+
])];
|
|
570
|
+
const verificationCall = verificationKinds.length > 0;
|
|
361
571
|
if (verificationCall) {
|
|
362
572
|
const callDigest = successfulToolCallDigest(turnId, toolCallId, toolName);
|
|
363
573
|
if (!successfulToolCallDigests.includes(callDigest)) successfulToolCallDigests.push(callDigest);
|
|
364
574
|
if (successfulToolCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolCallDigests.shift();
|
|
575
|
+
for (const kind of verificationKinds) {
|
|
576
|
+
const typedDigest = successfulVerificationCallDigest(turnId, toolCallId, toolName, kind);
|
|
577
|
+
if (!successfulVerificationCallDigests.includes(typedDigest)) {
|
|
578
|
+
successfulVerificationCallDigests.push(typedDigest);
|
|
579
|
+
}
|
|
580
|
+
if (successfulVerificationCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
|
|
581
|
+
successfulVerificationCallDigests.shift();
|
|
582
|
+
}
|
|
583
|
+
for (const scope of verificationScopes) {
|
|
584
|
+
const scopeDigest = successfulVerificationScopeDigest(
|
|
585
|
+
turnId, toolCallId, toolName, kind, scope,
|
|
586
|
+
);
|
|
587
|
+
if (!successfulVerificationScopeDigests.includes(scopeDigest)) {
|
|
588
|
+
successfulVerificationScopeDigests.push(scopeDigest);
|
|
589
|
+
}
|
|
590
|
+
if (successfulVerificationScopeDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
|
|
591
|
+
successfulVerificationScopeDigests.shift();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
365
595
|
}
|
|
366
596
|
const digest = crypto.createHash('sha256').update([
|
|
367
597
|
prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
|
|
368
|
-
|
|
598
|
+
verificationKinds.join(','), verificationScopes.join(','),
|
|
369
599
|
].join('\0')).digest('hex').slice(0, 16);
|
|
370
600
|
return Object.freeze({
|
|
371
601
|
turnId,
|
|
@@ -374,6 +604,8 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
374
604
|
failedTools: prior.failedTools + (successful ? 0 : 1),
|
|
375
605
|
successfulToolDigests: Object.freeze(successfulToolDigests),
|
|
376
606
|
successfulToolCallDigests: Object.freeze(successfulToolCallDigests),
|
|
607
|
+
successfulVerificationCallDigests: Object.freeze(successfulVerificationCallDigests),
|
|
608
|
+
successfulVerificationScopeDigests: Object.freeze(successfulVerificationScopeDigests),
|
|
377
609
|
digest,
|
|
378
610
|
});
|
|
379
611
|
}
|
|
@@ -490,10 +722,18 @@ function projectActionCheckpoint(checkpoint) {
|
|
|
490
722
|
const subject = value.verificationProof.subject === undefined
|
|
491
723
|
? ''
|
|
492
724
|
: ` [${value.verificationProof.subject}]`;
|
|
493
|
-
|
|
725
|
+
const kind = value.verificationProof.kind === undefined
|
|
726
|
+
? ''
|
|
727
|
+
: ` [${value.verificationProof.kind}]`;
|
|
728
|
+
const scope = value.verificationProof.scope === undefined
|
|
729
|
+
? ''
|
|
730
|
+
: ` [scope: ${value.verificationProof.scope}]`;
|
|
731
|
+
lines.push(`Verification proof${subject}${kind}${scope}: ${call} - ${value.verificationProof.claim}`);
|
|
494
732
|
if (value.verificationProof.sharpnessProof !== undefined) {
|
|
495
733
|
const sharpness = value.verificationProof.sharpnessProof;
|
|
496
|
-
|
|
734
|
+
const sharpnessKind = sharpness.kind === undefined ? '' : ` [${sharpness.kind}]`;
|
|
735
|
+
const sharpnessScope = sharpness.scope === undefined ? '' : ` [scope: ${sharpness.scope}]`;
|
|
736
|
+
lines.push(`Sharpness proof${sharpnessKind}${sharpnessScope}: ${sharpness.toolName} call ${sharpness.toolCallId} - ${sharpness.claim}`);
|
|
497
737
|
}
|
|
498
738
|
}
|
|
499
739
|
if (value.nextTrigger !== undefined) {
|
|
@@ -534,5 +774,7 @@ module.exports = {
|
|
|
534
774
|
normalizeVerificationProof,
|
|
535
775
|
projectActionCheckpoint,
|
|
536
776
|
successfulToolCallDigest,
|
|
777
|
+
successfulVerificationCallDigest,
|
|
537
778
|
successfulToolDigest,
|
|
779
|
+
verificationKindsForToolCall,
|
|
538
780
|
};
|
|
@@ -30,6 +30,14 @@ function verificationProofGaps(checkpoint) {
|
|
|
30
30
|
.test(String(error?.message ?? ''))) {
|
|
31
31
|
return ['The verifier sharpness proof does not match a successful current-turn verification call.'];
|
|
32
32
|
}
|
|
33
|
+
if (/(?:verificationProof|sharpnessProof) kind must match the successful current-turn verification call/u
|
|
34
|
+
.test(String(error?.message ?? ''))) {
|
|
35
|
+
return ['The completion proof kind exceeds what its exact verification call measured.'];
|
|
36
|
+
}
|
|
37
|
+
if (/(?:verificationProof|sharpnessProof) scope must match the successful current-turn verification call target/u
|
|
38
|
+
.test(String(error?.message ?? ''))) {
|
|
39
|
+
return ['Bind the completion proof scope to the exact target measured by its successful current-turn verification call.'];
|
|
40
|
+
}
|
|
33
41
|
if (/action-only tool/u.test(String(error?.message ?? ''))) {
|
|
34
42
|
return ['The completion proof names an action-only tool, not a verification tool.'];
|
|
35
43
|
}
|
package/blun.mjs
CHANGED
|
@@ -260352,10 +260352,14 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
|
|
|
260352
260352
|
subject: _enum(["result", "verifier"]),
|
|
260353
260353
|
toolCallId: string().min(1).max(256),
|
|
260354
260354
|
toolName: string().min(1).max(128),
|
|
260355
|
+
kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
|
|
260356
|
+
scope: string().min(1).max(256),
|
|
260355
260357
|
claim: string().min(1).max(512),
|
|
260356
260358
|
sharpnessProof: object({
|
|
260357
260359
|
toolCallId: string().min(1).max(256),
|
|
260358
260360
|
toolName: string().min(1).max(128),
|
|
260361
|
+
kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
|
|
260362
|
+
scope: string().min(1).max(256),
|
|
260359
260363
|
claim: string().min(1).max(512)
|
|
260360
260364
|
}).strict().optional()
|
|
260361
260365
|
}).strict().superRefine((value, ctx) => {
|
|
@@ -261692,6 +261696,17 @@ function telemetryToolErrorType(result) {
|
|
|
261692
261696
|
function toolResultText(result) {
|
|
261693
261697
|
return toolOutputText(result.output);
|
|
261694
261698
|
}
|
|
261699
|
+
function explicitToolResultEvidenceKinds(result) {
|
|
261700
|
+
const text = toolResultText(result);
|
|
261701
|
+
return /(?:^|\s)BLUN_EVIDENCE_KIND=reachability(?:\s|$)/u.test(text) ? ["reachability"] : [];
|
|
261702
|
+
}
|
|
261703
|
+
function explicitToolResultEvidenceScopes(result) {
|
|
261704
|
+
const text = toolResultText(result);
|
|
261705
|
+
return [...text.matchAll(/(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu)]
|
|
261706
|
+
.map((match) => match[1])
|
|
261707
|
+
.filter((scope, index, scopes) => scopes.indexOf(scope) === index)
|
|
261708
|
+
.slice(0, 5);
|
|
261709
|
+
}
|
|
261695
261710
|
function abandonedToolResultOutput(ended) {
|
|
261696
261711
|
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.`;
|
|
261697
261712
|
}
|
|
@@ -262794,7 +262809,9 @@ var init_turn = __esmMin((() => {
|
|
|
262794
262809
|
toolName: started.name,
|
|
262795
262810
|
outcome,
|
|
262796
262811
|
durationMs: Date.now() - started.startedAt,
|
|
262797
|
-
toolArgs: started.args
|
|
262812
|
+
toolArgs: started.args,
|
|
262813
|
+
resultEvidenceKinds: explicitToolResultEvidenceKinds(event.result),
|
|
262814
|
+
resultEvidenceScopes: explicitToolResultEvidenceScopes(event.result)
|
|
262798
262815
|
});
|
|
262799
262816
|
this.agent.telemetry.track("tool_call", properties);
|
|
262800
262817
|
this.agent.feedRootMissionContract("result", {
|
|
@@ -262900,6 +262917,8 @@ var update_goal_default;
|
|
|
262900
262917
|
var init_update_goal$1 = __esmMin((() => {
|
|
262901
262918
|
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";
|
|
262902
262919
|
update_goal_default += "\nBefore completing a goal with a criterion, bind the verified claim to the exact successful current-turn verification call in `verificationProof`, including its `toolCallId`. A write, edit, copy, deploy, or other action is not proof that the changed behavior works, even when it shares a mixed-use tool such as `Bash` with tests. Use `subject: result` for a result, report, measurement, or download. Use `subject: verifier` only when the new or changed test, gate, harness, or detector itself is the completion subject; then bind `sharpnessProof` to a separate successful current-turn counterexample or mutation call. Do not require a red probe for a normal report or measurement.\n";
|
|
262920
|
+
update_goal_default += "\nSet the proof `kind` to the exact capability of that call: `inspection` reads or searches, `integrity` compares bytes or hashes, `syntax` parses or type-checks, and `test` runs assertions. None of these alone proves a stronger kind. Use `reachability` only for a successful runtime probe that actually invokes the changed path and emits the exact marker `BLUN_EVIDENCE_KIND=reachability` after its assertions; loading a module without reaching the changed path is not reachability.\n";
|
|
262921
|
+
update_goal_default += "\nBind each proof `scope` to the exact target measured by that successful call, never to a free-text claim or intended file. Read and search tools derive scope from their target arguments. For shell or command tools, include the same safe token `BLUN_EVIDENCE_SCOPE=<scope>` in the launched non-mutating verification command and emit it only after that exact target succeeds; the runtime requires both sides.\n";
|
|
262903
262922
|
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";
|
|
262904
262923
|
}));
|
|
262905
262924
|
//#endregion
|