blun-king-cli 9.1.443 → 9.1.445

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.
@@ -6,6 +6,7 @@ const {
6
6
 
7
7
  const DEFAULT_MAX_CHARS = 3_000;
8
8
  const DEFAULT_MAX_SECTIONS = 5;
9
+ const GOAL_MISTAKE_QUERY_MAX_CHARS = 2_000;
9
10
  const RECENT_USER_MESSAGES = 4;
10
11
  const NON_ACTIONABLE_USER_ORIGINS = new Set([
11
12
  'background_task',
@@ -86,6 +87,55 @@ function recentUserText(history, limit = RECENT_USER_MESSAGES) {
86
87
  .join('\n');
87
88
  }
88
89
 
90
+ function boundedQueryValue(value, maxChars = 320) {
91
+ const text = String(value ?? '')
92
+ .replace(/[\u0000-\u001f\u007f]+/gu, ' ')
93
+ .replace(/\s+/gu, ' ')
94
+ .trim();
95
+ if (!text) return '';
96
+ return text.length <= maxChars ? text : `${text.slice(0, maxChars - 3).trimEnd()}...`;
97
+ }
98
+
99
+ function goalMistakeQuery(goal) {
100
+ if (!goal || typeof goal !== 'object' || Array.isArray(goal) || goal.status !== 'active') return '';
101
+ const objective = boundedQueryValue(goal.objective, 512);
102
+ if (!objective) return '';
103
+ const checkpoint = goal.actionCheckpoint && typeof goal.actionCheckpoint === 'object'
104
+ && !Array.isArray(goal.actionCheckpoint) ? goal.actionCheckpoint : null;
105
+ const frame = checkpoint?.problemFrame && typeof checkpoint.problemFrame === 'object'
106
+ && !Array.isArray(checkpoint.problemFrame) ? checkpoint.problemFrame : null;
107
+ const lines = [`Goal: ${objective}`];
108
+ const add = (label, value, maxChars = 320) => {
109
+ const bounded = boundedQueryValue(value, maxChars);
110
+ if (bounded) lines.push(`${label}: ${bounded}`);
111
+ };
112
+ add('Completion criterion', goal.completionCriterion, 384);
113
+ add('Phase', checkpoint?.phase, 32);
114
+ add('Last verified', checkpoint?.lastVerified);
115
+ add('Next action', checkpoint?.nextAction);
116
+ const gaps = Array.isArray(frame?.missingKnowledge)
117
+ ? frame.missingKnowledge.slice(0, 5).map((item) => boundedQueryValue(item, 256)).filter(Boolean)
118
+ : [];
119
+ if (gaps.length > 0) lines.push(`Missing knowledge: ${gaps.join(' | ')}`);
120
+ add('Selected action', frame?.selectedAction);
121
+ add('Selection reason', frame?.selectionReason);
122
+ add('Support choice', frame?.supportChoice, 256);
123
+ add('Risk', frame?.risk);
124
+ add('Expected evidence', checkpoint?.expectedEvidence, 384);
125
+ return lines.join('\n').slice(0, GOAL_MISTAKE_QUERY_MAX_CHARS).trimEnd();
126
+ }
127
+
128
+ function mistakeQueryTextForTurn(history, goal) {
129
+ const latestInput = Array.isArray(history) ? history.findLast(isActionableMistakeInput) : undefined;
130
+ const isGoalContinuation = latestInput?.origin?.kind === 'system_trigger'
131
+ && latestInput.origin.name === 'goal_continuation';
132
+ if (isGoalContinuation) {
133
+ const query = goalMistakeQuery(goal);
134
+ if (query) return query;
135
+ }
136
+ return recentUserText(history);
137
+ }
138
+
89
139
  function isLowInformationMistakeTurn(history) {
90
140
  if (!Array.isArray(history)) return false;
91
141
  const message = history.findLast(isActionableMistakeInput);
@@ -258,8 +308,10 @@ function selectRelevantMistakeContent(source, query, options = {}) {
258
308
  module.exports = {
259
309
  DEFAULT_MAX_CHARS,
260
310
  DEFAULT_MAX_SECTIONS,
311
+ GOAL_MISTAKE_QUERY_MAX_CHARS,
261
312
  RECENT_USER_MESSAGES,
262
313
  isLowInformationMistakeTurn,
314
+ mistakeQueryTextForTurn,
263
315
  recentUserText,
264
316
  semanticSignalTags,
265
317
  selectRelevantMistakeContent,
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const SCHEMA_VERSION = 1;
8
+ const MAX_LESSONS = 5;
9
+ const MAX_HEADING_CHARS = 160;
10
+ const MAX_SEMANTIC_MATCHES = 6;
11
+ const CANDIDATE_ID = /^vl-[a-f0-9]{32}$/u;
12
+
13
+ function cleanText(value, maxChars) {
14
+ const text = String(value ?? '')
15
+ .replace(/[\u0000-\u001f\u007f]+/gu, ' ')
16
+ .replace(/\s+/gu, ' ')
17
+ .trim();
18
+ return text.slice(0, maxChars);
19
+ }
20
+
21
+ function lessonKey(reference, heading) {
22
+ return crypto.createHash('sha256')
23
+ .update(`${String(reference ?? '')}\0${heading}`)
24
+ .digest('hex')
25
+ .slice(0, 24);
26
+ }
27
+
28
+ function normalizeLessons(value) {
29
+ if (!Array.isArray(value)) return [];
30
+ const lessons = [];
31
+ const seen = new Set();
32
+ for (const item of value) {
33
+ const heading = cleanText(item?.heading, MAX_HEADING_CHARS);
34
+ if (!heading) continue;
35
+ const key = lessonKey(item?.reference, heading);
36
+ if (seen.has(key)) continue;
37
+ seen.add(key);
38
+ const semanticMatches = [...new Set(
39
+ (Array.isArray(item?.semanticMatches) ? item.semanticMatches : [])
40
+ .map((tag) => cleanText(tag, 48))
41
+ .filter(Boolean),
42
+ )].sort().slice(0, MAX_SEMANTIC_MATCHES);
43
+ lessons.push({
44
+ lessonKey: key,
45
+ heading,
46
+ ...(semanticMatches.length > 0 ? { semanticMatches } : {}),
47
+ });
48
+ if (lessons.length >= MAX_LESSONS) break;
49
+ }
50
+ return lessons;
51
+ }
52
+
53
+ function atomicCreate(filePath, value) {
54
+ const temporary = `${filePath}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`;
55
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600 });
56
+ try {
57
+ fs.linkSync(temporary, filePath);
58
+ return true;
59
+ } catch (error) {
60
+ if (error?.code !== 'EEXIST') throw error;
61
+ return false;
62
+ } finally {
63
+ fs.rmSync(temporary, { force: true });
64
+ }
65
+ }
66
+
67
+ function readTrace(filePath) {
68
+ try {
69
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ function recordValidatedLearningOutcomeTrace(candidate, selectedLessons, options = {}) {
76
+ if (candidate?.source !== 'current_turn'
77
+ || candidate.status !== 'pending'
78
+ || !CANDIDATE_ID.test(String(candidate.id ?? ''))
79
+ || typeof candidate.filePath !== 'string'
80
+ || !candidate.filePath) return null;
81
+ const lessons = normalizeLessons(selectedLessons);
82
+ if (lessons.length === 0) return null;
83
+
84
+ const directory = path.dirname(candidate.filePath);
85
+ const filePath = path.join(directory, `${candidate.id}.lesson-exposure.jsonl`);
86
+ const trace = {
87
+ schemaVersion: SCHEMA_VERSION,
88
+ type: 'lesson_exposure_red_green',
89
+ status: 'observed',
90
+ candidateId: candidate.id,
91
+ outcome: 'validated_red_green',
92
+ causalAttribution: false,
93
+ lessons,
94
+ recordedAt: new Date((options.now || Date.now)()).toISOString(),
95
+ };
96
+ const created = atomicCreate(filePath, trace);
97
+ return {
98
+ created,
99
+ filePath,
100
+ trace: created ? trace : readTrace(filePath),
101
+ };
102
+ }
103
+
104
+ module.exports = {
105
+ normalizeLessons,
106
+ recordValidatedLearningOutcomeTrace,
107
+ };
package/blun.mjs CHANGED
@@ -231582,16 +231582,17 @@ async function readProjectMistakeSources(agent) {
231582
231582
  }
231583
231583
  return sources;
231584
231584
  }
231585
- var MistakeMdInjector, isLowInformationMistakeTurn, recentUserText, selectRelevantMistakeSources;
231585
+ var MistakeMdInjector, isLowInformationMistakeTurn, mistakeQueryTextForTurn, selectRelevantMistakeSources;
231586
231586
  var init_mistake_md = __esmMin((() => {
231587
231587
  init_injector();
231588
231588
  init_mistake_md_writer();
231589
- ({ isLowInformationMistakeTurn, recentUserText, selectRelevantMistakeSources } = createRequire(import.meta.url)("./bin/mistake-relevance-policy.cjs"));
231589
+ ({ isLowInformationMistakeTurn, mistakeQueryTextForTurn, selectRelevantMistakeSources } = createRequire(import.meta.url)("./bin/mistake-relevance-policy.cjs"));
231590
231590
  MistakeMdInjector = class extends DynamicInjector {
231591
231591
  injectionVariant = "mistake_md";
231592
231592
  reserveChecked = false;
231593
231593
  mistakeDir;
231594
231594
  mistakeFile;
231595
+ selectedLessons = [];
231595
231596
  constructor(agent) {
231596
231597
  super(agent);
231597
231598
  this.mistakeDir = resolveMistakeDir(agent.blunHomeDir);
@@ -231600,6 +231601,10 @@ var init_mistake_md = __esmMin((() => {
231600
231601
  onContextClear() {
231601
231602
  super.onContextClear();
231602
231603
  this.reserveChecked = false;
231604
+ this.selectedLessons = [];
231605
+ }
231606
+ getSelectedLessons() {
231607
+ return this.selectedLessons.map((lesson) => ({ ...lesson }));
231603
231608
  }
231604
231609
  async inject() {
231605
231610
  const injection = await this.getInjection();
@@ -231619,6 +231624,7 @@ var init_mistake_md = __esmMin((() => {
231619
231624
  });
231620
231625
  }
231621
231626
  async getInjection() {
231627
+ this.selectedLessons = [];
231622
231628
  try {
231623
231629
  if (isLowInformationMistakeTurn(this.agent.context.history)) return void 0;
231624
231630
  const sources = await readProjectMistakeSources(this.agent);
@@ -231635,9 +231641,10 @@ var init_mistake_md = __esmMin((() => {
231635
231641
  content: sharedContent
231636
231642
  });
231637
231643
  if (sources.length === 0) return void 0;
231638
- const query = recentUserText(this.agent.context.history);
231644
+ const query = mistakeQueryTextForTurn(this.agent.context.history, this.agent.goal.getGoal().goal);
231639
231645
  const selected = selectRelevantMistakeSources(sources, query);
231640
231646
  if (!selected.text) return void 0;
231647
+ this.selectedLessons = selected.selected;
231641
231648
  let prefix = "";
231642
231649
  if (!this.reserveChecked && sharedContent) {
231643
231650
  this.reserveChecked = true;
@@ -232243,13 +232250,19 @@ function buildValidatedLearningSignalReminder(signal) {
232243
232250
  lines.push("</validated-learning-signal>");
232244
232251
  return lines.join("\n");
232245
232252
  }
232246
- var ValidatedLearningSignalInjector, shouldSyncValidatedLearningCandidate, syncValidatedLearningCandidate;
232253
+ var ValidatedLearningSignalInjector, recordValidatedLearningOutcomeTrace, shouldSyncValidatedLearningCandidate, syncValidatedLearningCandidate;
232247
232254
  var init_validated_learning_signal = __esmMin((() => {
232248
232255
  init_injector();
232249
232256
  ({ syncValidatedLearningCandidate } = createRequire(import.meta.url)("./bin/validated-learning-signal.cjs"));
232250
232257
  ({ shouldSyncValidatedLearningCandidate } = createRequire(import.meta.url)("./bin/validated-learning-performance-policy.cjs"));
232258
+ ({ recordValidatedLearningOutcomeTrace } = createRequire(import.meta.url)("./bin/validated-learning-outcome-trace.cjs"));
232251
232259
  ValidatedLearningSignalInjector = class extends DynamicInjector {
232252
232260
  injectionVariant = "validated_learning_signal";
232261
+ selectedLessons;
232262
+ constructor(agent, selectedLessons) {
232263
+ super(agent);
232264
+ this.selectedLessons = selectedLessons;
232265
+ }
232253
232266
  async inject() {
232254
232267
  const existing = this.agent.context.history.filter(isValidatedLearningSignalReminder);
232255
232268
  if (!shouldSyncValidatedLearningCandidate(this.agent.context.history)) return;
@@ -232262,6 +232275,9 @@ var init_validated_learning_signal = __esmMin((() => {
232262
232275
  if (existing.length > 0) this.agent.context.removeSystemRemindersMatching(isValidatedLearningSignalReminder);
232263
232276
  return;
232264
232277
  }
232278
+ if (signal.source === "current_turn") try {
232279
+ recordValidatedLearningOutcomeTrace(signal, this.selectedLessons?.() ?? []);
232280
+ } catch {}
232265
232281
  const injection = buildValidatedLearningSignalReminder(signal);
232266
232282
  const expected = `<system-reminder>\n${injection}\n</system-reminder>`;
232267
232283
  if (existing.length === 1 && reminderText(existing[0]) === expected) return;
@@ -233086,13 +233102,14 @@ var init_manager$2 = __esmMin((() => {
233086
233102
  missionContractInjector = null;
233087
233103
  constructor(agent) {
233088
233104
  this.agent = agent;
233105
+ const mistakeMdInjector = new MistakeMdInjector(agent);
233089
233106
  this.injectors = [
233090
233107
  new PluginSessionStartInjector(agent),
233091
233108
  new ErrorMemoryInjector(agent),
233092
233109
  new ToolAwarenessInjector(agent),
233093
- new MistakeMdInjector(agent),
233110
+ mistakeMdInjector,
233094
233111
  new RepeatedAssistantResponseInjector(agent),
233095
- new ValidatedLearningSignalInjector(agent),
233112
+ new ValidatedLearningSignalInjector(agent, () => mistakeMdInjector.getSelectedLessons()),
233096
233113
  new ActionStyleInjector(agent),
233097
233114
  new PlanModeInjector(agent),
233098
233115
  new PermissionModeInjector(agent)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.443",
3
+ "version": "9.1.445",
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": {