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.
- package/bin/mistake-relevance-policy.cjs +52 -0
- package/bin/validated-learning-outcome-trace.cjs +107 -0
- package/blun.mjs +23 -6
- package/package.json +1 -1
|
@@ -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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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)
|