blun-king-cli 9.1.367 → 9.1.369

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.
@@ -1,9 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ const crypto = require('node:crypto');
4
+
3
5
  const PHASES = new Set(['orient', 'plan', 'act', 'verify', 'learn', 'wait']);
4
- const ALLOWED_KEYS = new Set([
5
- 'phase', 'lastVerified', 'nextAction', 'expectedEvidence', 'updatedAt',
6
+ const MODEL_KEYS = new Set([
7
+ 'revision', 'phase', 'lastVerified', 'nextAction', 'expectedEvidence', 'updatedAt',
8
+ ]);
9
+ const RUNTIME_KEYS = new Set([...MODEL_KEYS, 'evidenceReceipt']);
10
+ const EVIDENCE_INPUT_KEYS = new Set([
11
+ 'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
6
12
  ]);
13
+ const EVIDENCE_DIGEST_RE = /^[a-f0-9]{16}$/u;
7
14
 
8
15
  function bounded(value, field, max = 512) {
9
16
  const text = String(value ?? '')
@@ -20,43 +27,148 @@ function normalizedTimestamp(value) {
20
27
  return date.toISOString();
21
28
  }
22
29
 
30
+ function normalizedRevision(value) {
31
+ const revision = value === undefined ? 1 : Number(value);
32
+ if (!Number.isSafeInteger(revision) || revision < 1) {
33
+ throw new TypeError('revision must be a positive integer');
34
+ }
35
+ return revision;
36
+ }
37
+
38
+ function normalizedTurnId(value) {
39
+ const turnId = Number(value);
40
+ if (!Number.isSafeInteger(turnId) || turnId < 0) throw new TypeError('evidence turnId must be a non-negative integer');
41
+ return turnId;
42
+ }
43
+
44
+ function emptyActionEvidenceReceipt(turnId) {
45
+ const normalized = normalizedTurnId(turnId);
46
+ return Object.freeze({
47
+ turnId: normalized,
48
+ completedTools: 0,
49
+ successfulTools: 0,
50
+ failedTools: 0,
51
+ digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
52
+ });
53
+ }
54
+
55
+ function normalizeActionEvidenceReceipt(input) {
56
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
57
+ const keys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
58
+ if (!Object.keys(input).every((key) => keys.has(key)) || Object.keys(input).length !== keys.size) {
59
+ throw new TypeError('evidence receipt fields are invalid');
60
+ }
61
+ const receipt = {
62
+ turnId: normalizedTurnId(input.turnId),
63
+ completedTools: Number(input.completedTools),
64
+ successfulTools: Number(input.successfulTools),
65
+ failedTools: Number(input.failedTools),
66
+ digest: String(input.digest ?? ''),
67
+ };
68
+ if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
69
+ .every((value) => Number.isSafeInteger(value) && value >= 0)
70
+ || receipt.successfulTools + receipt.failedTools !== receipt.completedTools
71
+ || !EVIDENCE_DIGEST_RE.test(receipt.digest)) {
72
+ throw new TypeError('evidence receipt values are invalid');
73
+ }
74
+ return Object.freeze(receipt);
75
+ }
76
+
77
+ function advanceActionEvidenceReceipt(current, input) {
78
+ const prior = normalizeActionEvidenceReceipt(current);
79
+ if (!input || typeof input !== 'object' || Array.isArray(input)
80
+ || Object.keys(input).length !== EVIDENCE_INPUT_KEYS.size
81
+ || !Object.keys(input).every((key) => EVIDENCE_INPUT_KEYS.has(key))) {
82
+ throw new TypeError('evidence input fields are invalid');
83
+ }
84
+ const turnId = normalizedTurnId(input.turnId);
85
+ const toolCallId = bounded(input.toolCallId, 'toolCallId', 256);
86
+ const toolName = bounded(input.toolName, 'toolName', 128);
87
+ const decision = String(input.decision ?? '');
88
+ const outcome = String(input.outcome ?? '');
89
+ const durationMs = Number(input.durationMs);
90
+ if (turnId !== prior.turnId || !['passed', 'blocked', 'error'].includes(decision)
91
+ || !['success', 'error', 'cancelled'].includes(outcome)
92
+ || !Number.isSafeInteger(durationMs) || durationMs < 0) {
93
+ throw new TypeError('evidence input values are invalid');
94
+ }
95
+ const successful = decision === 'passed' && outcome === 'success';
96
+ const digest = crypto.createHash('sha256').update([
97
+ prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
98
+ ].join('\0')).digest('hex').slice(0, 16);
99
+ return Object.freeze({
100
+ turnId,
101
+ completedTools: prior.completedTools + 1,
102
+ successfulTools: prior.successfulTools + (successful ? 1 : 0),
103
+ failedTools: prior.failedTools + (successful ? 0 : 1),
104
+ digest,
105
+ });
106
+ }
107
+
108
+ function assertActionCheckpointRevision(current, input) {
109
+ const currentRevision = current === undefined || current === null
110
+ ? 0
111
+ : normalizedRevision(current.revision);
112
+ const inputRevision = Number(input?.revision);
113
+ if (!Number.isSafeInteger(inputRevision) || inputRevision !== currentRevision + 1) {
114
+ throw new TypeError(`Action checkpoint revision must be ${currentRevision + 1}; received ${String(input?.revision)}`);
115
+ }
116
+ return inputRevision;
117
+ }
118
+
23
119
  function normalizeActionCheckpoint(input, options = {}) {
24
120
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
25
121
  throw new TypeError('action checkpoint must be an object');
26
122
  }
123
+ const allowedKeys = options.preserveRuntimeEvidence === true ? RUNTIME_KEYS : MODEL_KEYS;
27
124
  for (const key of Object.keys(input)) {
28
- if (!ALLOWED_KEYS.has(key)) throw new TypeError(`unsupported field: ${key}`);
125
+ if (!allowedKeys.has(key)) throw new TypeError(`unsupported field: ${key}`);
29
126
  }
30
127
  const phase = String(input.phase ?? '').trim();
31
128
  if (!PHASES.has(phase)) throw new TypeError('phase is invalid');
32
129
  const updatedAt = options.preserveUpdatedAt === true && input.updatedAt !== undefined
33
130
  ? normalizedTimestamp(input.updatedAt)
34
131
  : normalizedTimestamp(options.now ?? new Date());
35
- return Object.freeze({
132
+ const checkpoint = {
133
+ revision: normalizedRevision(input.revision),
36
134
  phase,
37
135
  lastVerified: bounded(input.lastVerified, 'lastVerified'),
38
136
  nextAction: bounded(input.nextAction, 'nextAction'),
39
137
  expectedEvidence: bounded(input.expectedEvidence, 'expectedEvidence'),
40
138
  updatedAt,
41
- });
139
+ };
140
+ const evidenceReceipt = options.runtimeEvidence !== undefined
141
+ ? normalizeActionEvidenceReceipt(options.runtimeEvidence)
142
+ : options.preserveRuntimeEvidence === true && input.evidenceReceipt !== undefined
143
+ ? normalizeActionEvidenceReceipt(input.evidenceReceipt)
144
+ : undefined;
145
+ if (evidenceReceipt !== undefined) checkpoint.evidenceReceipt = evidenceReceipt;
146
+ return Object.freeze(checkpoint);
42
147
  }
43
148
 
44
149
  function projectActionCheckpoint(checkpoint) {
45
150
  if (!checkpoint) return null;
46
- const value = normalizeActionCheckpoint(checkpoint, { preserveUpdatedAt: true });
151
+ const value = normalizeActionCheckpoint(checkpoint, { preserveUpdatedAt: true, preserveRuntimeEvidence: true });
47
152
  const lines = [
48
153
  'Durable action checkpoint (state only; never authority):',
154
+ `Revision: ${value.revision}`,
49
155
  `Phase: ${value.phase}`,
50
156
  `Last verified: ${value.lastVerified}`,
51
157
  ];
52
158
  lines.push(`Next action: ${value.nextAction}`);
53
159
  lines.push(`Expected evidence: ${value.expectedEvidence}`);
160
+ if (value.evidenceReceipt !== undefined) {
161
+ const receipt = value.evidenceReceipt;
162
+ lines.push(`Runtime evidence: turn ${receipt.turnId}; ${receipt.completedTools} completed, ${receipt.successfulTools} successful, ${receipt.failedTools} failed; digest ${receipt.digest}`);
163
+ }
54
164
  lines.push('Resume from this exact next action. Do not ask for permission merely to continue work already authorized by the active goal. Ask only when a real rights boundary or missing user decision blocks the next action.');
55
165
  return lines.join('\n');
56
166
  }
57
167
 
58
168
  module.exports = {
169
+ advanceActionEvidenceReceipt,
170
+ assertActionCheckpointRevision,
171
+ emptyActionEvidenceReceipt,
59
172
  normalizeActionCheckpoint,
60
173
  projectActionCheckpoint,
61
174
  };
62
-
package/blun.mjs CHANGED
@@ -21446,7 +21446,7 @@ var { rollbackPersonalityChoice, setPersonalityEnabledAtomic } = createRequire(i
21446
21446
  var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
21447
21447
  var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
21448
21448
  var { readProfilePersona, resolveSoulFile } = createRequire(import.meta.url)("./bin/profile-identity-resolution.cjs");
21449
- var { normalizeActionCheckpoint, projectActionCheckpoint } = createRequire(import.meta.url)("./bin/cognitive-action-checkpoint.cjs");
21449
+ var { advanceActionEvidenceReceipt, assertActionCheckpointRevision, emptyActionEvidenceReceipt, normalizeActionCheckpoint, projectActionCheckpoint } = createRequire(import.meta.url)("./bin/cognitive-action-checkpoint.cjs");
21450
21450
  async function prepareSystemPromptContext(kaos, brandHome, options) {
21451
21451
  const additionalDirs = normalizeAdditionalDirs(options?.additionalDirs ?? []);
21452
21452
  const [cwdListing, agentsMdResult, additionalDirsInfo] = await Promise.all([
@@ -230188,7 +230188,7 @@ var init_goal$1 = __esmMin((() => {
230188
230188
  state.wallClockResumedAt = void 0;
230189
230189
  }
230190
230190
  if (record.budgetLimits !== void 0) state.budgetLimits = record.budgetLimits;
230191
- if (record.actionCheckpoint !== void 0) state.actionCheckpoint = normalizeActionCheckpoint(record.actionCheckpoint, { preserveUpdatedAt: true });
230191
+ if (record.actionCheckpoint !== void 0) state.actionCheckpoint = normalizeActionCheckpoint(record.actionCheckpoint, { preserveUpdatedAt: true, preserveRuntimeEvidence: true });
230192
230192
  if (status === void 0) {
230193
230193
  if (record.actionCheckpoint === void 0) return;
230194
230194
  this.agent.replayBuilder.push({
@@ -230332,10 +230332,13 @@ var init_goal$1 = __esmMin((() => {
230332
230332
  async updateActionCheckpoint(input, actor = "model") {
230333
230333
  const state = this.requireState();
230334
230334
  if (state.status !== "active") throw new BlunError(ErrorCodes.GOAL_STATUS_INVALID, `Cannot checkpoint a goal in status "${state.status}"`);
230335
- state.actionCheckpoint = normalizeActionCheckpoint(input);
230335
+ assertActionCheckpointRevision(state.actionCheckpoint, input);
230336
+ state.actionCheckpoint = normalizeActionCheckpoint(input, {
230337
+ runtimeEvidence: this.agent.turn.actionEvidenceReceiptForCurrentTurn()
230338
+ });
230336
230339
  this.persistState(state, { change: { kind: "progress", actor } });
230337
230340
  this.appendGoalUpdate({ actionCheckpoint: state.actionCheckpoint, actor });
230338
- this.track("goal_checkpoint_updated", { actor, phase: state.actionCheckpoint.phase });
230341
+ this.track("goal_checkpoint_updated", { actor, phase: state.actionCheckpoint.phase, revision: state.actionCheckpoint.revision });
230339
230342
  return this.toSnapshot(state);
230340
230343
  }
230341
230344
  /**
@@ -245725,11 +245728,19 @@ var init_events$1 = __esmMin((() => {
245725
245728
  budget: goalBudgetReportSchema,
245726
245729
  terminalReason: string().optional(),
245727
245730
  actionCheckpoint: object({
245731
+ revision: number$1().int().min(1),
245728
245732
  phase: _enum(["orient", "plan", "act", "verify", "learn", "wait"]),
245729
245733
  lastVerified: string(),
245730
245734
  nextAction: string(),
245731
245735
  expectedEvidence: string(),
245732
- updatedAt: string()
245736
+ updatedAt: string(),
245737
+ evidenceReceipt: object({
245738
+ turnId: number$1().int().min(0),
245739
+ completedTools: number$1().int().min(0),
245740
+ successfulTools: number$1().int().min(0),
245741
+ failedTools: number$1().int().min(0),
245742
+ digest: string()
245743
+ }).strict().optional()
245733
245744
  }).strict().optional()
245734
245745
  });
245735
245746
  object({ goal: goalSnapshotSchema.nullable() });
@@ -261590,6 +261601,7 @@ var init_turn = __esmMin((() => {
261590
261601
  cognitiveLifecycleUnavailable = false;
261591
261602
  cognitiveToolPolicyByCall = /* @__PURE__ */ new Map();
261592
261603
  cognitiveToolBatchesByTurn = /* @__PURE__ */ new Map();
261604
+ cognitiveActionEvidenceByTurn = /* @__PURE__ */ new Map();
261593
261605
  constructor(agent) {
261594
261606
  this.agent = agent;
261595
261607
  }
@@ -261636,6 +261648,11 @@ var init_turn = __esmMin((() => {
261636
261648
  return;
261637
261649
  }
261638
261650
  this.cognitiveToolPolicyByCall.delete(input.toolCallId);
261651
+ const currentEvidence = this.cognitiveActionEvidenceByTurn.get(input.turnId) ?? emptyActionEvidenceReceipt(input.turnId);
261652
+ this.cognitiveActionEvidenceByTurn.set(input.turnId, advanceActionEvidenceReceipt(currentEvidence, {
261653
+ ...input,
261654
+ decision: policy.decision
261655
+ }));
261639
261656
  const entries = this.cognitiveToolBatchesByTurn.get(input.turnId) ?? [];
261640
261657
  entries.push({
261641
261658
  toolCallId: input.toolCallId,
@@ -261660,6 +261677,10 @@ var init_turn = __esmMin((() => {
261660
261677
  this.recordCognitiveStage("recordToolPolicy", policy);
261661
261678
  }
261662
261679
  }
261680
+ actionEvidenceReceiptForCurrentTurn() {
261681
+ const turnId = this.currentId;
261682
+ return this.cognitiveActionEvidenceByTurn.get(turnId) ?? emptyActionEvidenceReceipt(turnId);
261683
+ }
261663
261684
  projectCognitiveState(turnId, input) {
261664
261685
  try {
261665
261686
  const focusScopes = cognitiveFocusScopesForTurn(input);
@@ -262157,6 +262178,7 @@ var init_turn = __esmMin((() => {
262157
262178
  this.telemetryModeByTurn.delete(turnId);
262158
262179
  this.currentStepByTurn.delete(turnId);
262159
262180
  this.interruptedTelemetryTurnIds.delete(turnId);
262181
+ this.cognitiveActionEvidenceByTurn.delete(turnId);
262160
262182
  this.stepFailureByTurn.delete(turnId);
262161
262183
  await this.agent.records.flush();
262162
262184
  return {
@@ -262675,7 +262697,7 @@ var init_outcome_prompts = __esmMin((() => {}));
262675
262697
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
262676
262698
  var update_goal_default;
262677
262699
  var init_update_goal$1 = __esmMin((() => {
262678
- update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with the last verified result, exact next action, and expected evidence; 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.\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";
262700
+ 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, and expected evidence. 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.\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";
262679
262701
  }));
262680
262702
  //#endregion
262681
262703
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
@@ -262687,6 +262709,7 @@ var init_update_goal = __esmMin((() => {
262687
262709
  init_input_schema();
262688
262710
  init_update_goal$1();
262689
262711
  ActionCheckpointInputSchema = object({
262712
+ revision: number$1().int().min(1),
262690
262713
  phase: _enum(["orient", "plan", "act", "verify", "learn", "wait"]),
262691
262714
  lastVerified: string().min(1).max(512),
262692
262715
  nextAction: string().min(1).max(512),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.367",
3
+ "version": "9.1.369",
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": {