blun-king-cli 9.1.599 → 9.1.600
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/CHANGELOG.md +7 -0
- package/README.md +13 -3
- package/agent-spine-plugin/CHANGELOG.md +1 -1
- package/agent-spine-plugin/docs/host-integration.md +1 -15
- package/agent-spine-plugin/docs/preflight-recall.md +1 -1
- package/agent-spine-plugin/scripts/check-install.js +1 -6
- package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
- package/agent-spine-plugin/scripts/release-check.js +2 -3
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
- package/agent-spine-plugin/src/hook.js +40 -40
- package/agent-spine-plugin/src/lib/briefing.js +6 -3
- package/agent-spine-plugin/src/lib/hook-context.js +63 -64
- package/agent-spine-plugin/src/lib/hook-output.js +115 -94
- package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
- package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
- package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
- package/agent-spine-plugin/src/lib/source-roots.js +26 -16
- package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
- package/bin/core-bootstrap.js +18 -10
- package/bin/launcher-mode.js +5 -0
- package/bin/launcher-runtime.js +27 -0
- package/bin/verify-bundled-agent-sources.cjs +28 -2
- package/blun.mjs +127 -29
- package/bundled-agent-sources.json +192 -155
- package/package.json +1 -1
- package/standard-skills/translate-native/README.md +1334 -1
- package/standard-skills/translate-native/SKILL.md +1 -1
- package/standard-skills/translate-native/VERSION +1 -1
- package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
- package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
- package/standard-skills/translate-native/integrations/website_localization.py +234 -10
- package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
- package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
- package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
- package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
- package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
- package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
- package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
- package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
- package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
- package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
- package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
- package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
- package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
- package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
- package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
- package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
- package/standard-skills/translate-native/provenance.json +2 -2
- package/standard-skills/translate-native/references/commercial-localization.md +96 -17
- package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
- package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
- package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/VERSION +1 -1
- package/standard-tools/language-guard/blun_language_guard.py +3 -1
- package/standard-tools/language-guard/check_commercial_review.py +69 -9
- package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/provenance.json +2 -2
- package/standard-tools/manifest.json +1 -1
- package/worker-host.mjs +115 -22
- package/agent-spine-plugin/scripts/check-install-king.js +0 -37
|
@@ -1,99 +1,119 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const MAX_BYTES=1200;
|
|
2
|
+
const BOUND="\n[optional runtime detail omitted: 1200-byte bound]";
|
|
3
|
+
const bytes=value=>Buffer.byteLength(JSON.stringify(value));
|
|
3
4
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function addRecallField(packet, key, value, maximum = 1020) {
|
|
9
|
-
if (value === undefined || value === null) return;
|
|
10
|
-
packet[key] = value;
|
|
11
|
-
if (byteSize(packet) <= maximum) return true;
|
|
5
|
+
function addRecallField(packet,key,value,maximum=1020){
|
|
6
|
+
if(value===undefined||value===null)return;
|
|
7
|
+
packet[key]=value;
|
|
8
|
+
if(bytes(packet)<=maximum)return true;
|
|
12
9
|
delete packet[key];
|
|
13
10
|
return false;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
function sharedPrefix(values)
|
|
17
|
-
let prefix
|
|
18
|
-
for
|
|
19
|
-
while
|
|
13
|
+
function sharedPrefix(values){
|
|
14
|
+
let prefix=values[0]||"";
|
|
15
|
+
for(const value of values.slice(1)){
|
|
16
|
+
while(prefix&&!value.startsWith(prefix))prefix=prefix.slice(0,-1);
|
|
20
17
|
}
|
|
21
|
-
return prefix.length
|
|
18
|
+
return prefix.length>=4?prefix:"";
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
function
|
|
25
|
-
if
|
|
26
|
-
if
|
|
27
|
-
const digest
|
|
28
|
-
if
|
|
29
|
-
candidates.common
|
|
30
|
-
candidates.fields.splice(digest,
|
|
31
|
-
for
|
|
21
|
+
function compactRows(packet,candidates){
|
|
22
|
+
if(candidates.rows.length<2)return;
|
|
23
|
+
if(!candidates.prefixes){
|
|
24
|
+
const digest=candidates.fields.indexOf("digest");
|
|
25
|
+
if(digest>=0&&candidates.rows.every(row=>row[digest]===candidates.rows[0][digest])){
|
|
26
|
+
candidates.common={...(candidates.common||{}),digest:candidates.rows[0][digest]};
|
|
27
|
+
candidates.fields.splice(digest,1);
|
|
28
|
+
for(const row of candidates.rows)row.splice(digest,1);
|
|
32
29
|
}
|
|
33
|
-
candidates.prefixes
|
|
34
|
-
const values
|
|
35
|
-
const prefix
|
|
36
|
-
:
|
|
37
|
-
?
|
|
38
|
-
if
|
|
39
|
-
for
|
|
30
|
+
candidates.prefixes=candidates.fields.map((field,index)=>{
|
|
31
|
+
const values=candidates.rows.map(row=>row[index]);
|
|
32
|
+
const prefix=["id","message","session"].includes(field)?sharedPrefix(values)
|
|
33
|
+
:field==="at"&&values.every(value=>/^\d{4}-\d{2}-\d{2}T\d{2}:/.test(value))
|
|
34
|
+
?values[0].slice(0,14):"";
|
|
35
|
+
if(!prefix||!values.every(value=>value.startsWith(prefix)))return"";
|
|
36
|
+
for(const row of candidates.rows)row[index]=row[index].slice(prefix.length);
|
|
40
37
|
return prefix;
|
|
41
38
|
});
|
|
42
39
|
}
|
|
43
|
-
if
|
|
40
|
+
if(packet.task){
|
|
44
41
|
delete packet.task.status;
|
|
45
|
-
if
|
|
42
|
+
if(packet.task.lastVerifiedStep)delete packet.task.lastVerifiedStep.result;
|
|
46
43
|
}
|
|
47
|
-
const review
|
|
48
|
-
if
|
|
49
|
-
status:
|
|
50
|
-
question:
|
|
44
|
+
const review=packet.feedbackReview;
|
|
45
|
+
if(review?.modelClarification)packet.feedbackReview={
|
|
46
|
+
status:review.status,completionVerified:false,target:review.targetAssertionId,
|
|
47
|
+
question:review.modelClarification.question,model:true
|
|
51
48
|
};
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
function
|
|
55
|
-
const candidates
|
|
56
|
-
const proposal
|
|
57
|
-
if
|
|
58
|
-
if
|
|
59
|
-
&&
|
|
60
|
-
|
|
51
|
+
function compactFeedback(packet){
|
|
52
|
+
const candidates=packet.feedbackCandidates;
|
|
53
|
+
const proposal=packet.feedbackReview?.modelClarification;
|
|
54
|
+
if(proposal)for(const key of ["completionVerified","provider","replaces"])delete proposal[key];
|
|
55
|
+
if(candidates&&!Array.isArray(candidates)
|
|
56
|
+
&&Array.isArray(candidates.fields)&&Array.isArray(candidates.rows)){
|
|
57
|
+
compactRows(packet,candidates);
|
|
61
58
|
return true;
|
|
62
59
|
}
|
|
63
|
-
if
|
|
64
|
-
const origins
|
|
65
|
-
const sharedKeys
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
packet.feedbackCandidates = {
|
|
60
|
+
if(!Array.isArray(candidates)||candidates.length<2)return false;
|
|
61
|
+
const origins=["provider","session"];
|
|
62
|
+
const sharedKeys=origins.filter(key=>candidates.every(item=>item?.[key]===candidates[0]?.[key]));
|
|
63
|
+
const fields=["id","text","digest","message","at",
|
|
64
|
+
...origins.filter(key=>!sharedKeys.includes(key))];
|
|
65
|
+
packet.feedbackCandidates={
|
|
70
66
|
fields,
|
|
71
|
-
...(sharedKeys.length
|
|
72
|
-
[key,
|
|
73
|
-
rows:
|
|
67
|
+
...(sharedKeys.length?{common:Object.fromEntries(sharedKeys.map(key=>
|
|
68
|
+
[key,candidates[0][key]]))}:{}),
|
|
69
|
+
rows:candidates.map(item=>fields.map(key=>item[key]))
|
|
74
70
|
};
|
|
75
|
-
|
|
71
|
+
compactRows(packet,packet.feedbackCandidates);
|
|
76
72
|
return true;
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
if
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
function timelineRecall(t){
|
|
76
|
+
if(!t.events?.length)return{status:t.status,reason:t.reason,awaited:t.awaited,
|
|
77
|
+
reads:t.sourceReads,completionVerified:false,omitted:t.omittedEvents||0};
|
|
78
|
+
return{status:t.status,awaited:t.awaited,reads:t.sourceReads,completionVerified:false,
|
|
79
|
+
naturalMessages:"unresolved",trust:"untrusted-session-history",
|
|
80
|
+
...(t.source?{source:t.source}:{sources:t.sources}),prefixes:t.prefixes,
|
|
81
|
+
fields:t.fields,rows:t.events.map(row=>[...row]),omitted:t.omittedEvents||0}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function preAnswerRecallCapsule(detailed){
|
|
85
|
+
if(detailed?.event!=="UserPromptSubmit"||!detailed.loaded)return null;
|
|
86
|
+
const briefing=detailed.briefing;
|
|
87
|
+
const preflight=detailed.preflight?.briefing;
|
|
88
|
+
if(!briefing||!preflight)return null;
|
|
89
|
+
if(briefing.scope?.groupId!==null&&briefing.scope?.groupId!==undefined)return null;
|
|
90
|
+
const omitted=[];
|
|
91
|
+
const packet=briefing.preAnswerRecall?structuredClone(briefing.preAnswerRecall):{
|
|
92
|
+
schema:"agentspine.pre-answer-recall/v1",
|
|
93
|
+
order:"review-before-claims-and-actions",
|
|
94
|
+
authority:"context-only"
|
|
90
95
|
};
|
|
96
|
+
const timeline=detailed.sourceResolution?.timeline?.preAnswerRecall;
|
|
97
|
+
if(timeline?.awaited){
|
|
98
|
+
let candidate=timelineRecall(timeline);
|
|
99
|
+
while(!addRecallField(packet,"timeline",candidate)&&candidate.rows?.length){
|
|
100
|
+
candidate.rows.pop();
|
|
101
|
+
candidate.omitted += 1;
|
|
102
|
+
if(!candidate.rows.length)candidate={status:"unavailable",reason:"host-context-budget",
|
|
103
|
+
awaited:candidate.awaited,reads:candidate.reads,completionVerified:false,omitted:candidate.omitted};
|
|
104
|
+
}
|
|
105
|
+
if(!packet.timeline&&bytes(packet)>MAX_BYTES){
|
|
106
|
+
const keys=Object.keys(packet).filter(key=>!/^schema$|^order$|^authority$/.test(key));
|
|
107
|
+
for(const key of keys)delete packet[key];
|
|
108
|
+
packet.timeline=candidate;packet.omitted=keys;
|
|
109
|
+
}else if(!packet.timeline)packet.timelineOmitted=true;
|
|
110
|
+
}
|
|
91
111
|
const mustRemember = (preflight.mustRemember || []).map((item) => ({
|
|
92
112
|
id: item.id, claim: item.claim, checksum: item.checksum
|
|
93
113
|
}));
|
|
94
114
|
if (mustRemember.length && !addRecallField(packet, "mustRemember", mustRemember)) {
|
|
95
|
-
if (!
|
|
96
|
-
|| !addRecallField(packet, "mustRemember", mustRemember,
|
|
115
|
+
if (!compactFeedback(packet)
|
|
116
|
+
|| !addRecallField(packet, "mustRemember", mustRemember, MAX_BYTES)) {
|
|
97
117
|
omitted.push("mustRemember");
|
|
98
118
|
}
|
|
99
119
|
}
|
|
@@ -101,13 +121,13 @@ export function preAnswerRecallCapsule(detailed) {
|
|
|
101
121
|
(provider.items || []).map((item) => ({ providerId: provider.providerId,
|
|
102
122
|
id: item.id, revision: item.revision, claim: item.claim, source: item.source }))).slice(0, 3);
|
|
103
123
|
if (retrieval.length && !addRecallField(packet, "retrieval", retrieval)) {
|
|
104
|
-
if (!
|
|
105
|
-
|| !addRecallField(packet, "retrieval", retrieval,
|
|
124
|
+
if (!compactFeedback(packet)
|
|
125
|
+
|| !addRecallField(packet, "retrieval", retrieval, MAX_BYTES)) {
|
|
106
126
|
omitted.push("retrieval");
|
|
107
127
|
}
|
|
108
128
|
}
|
|
109
|
-
|
|
110
|
-
if
|
|
129
|
+
compactFeedback(packet);
|
|
130
|
+
if(omitted.length&&bytes({...packet,omitted})<=MAX_BYTES)packet.omitted=omitted;
|
|
111
131
|
return Object.keys(packet).length > 3 ? packet : null;
|
|
112
132
|
}
|
|
113
133
|
|
|
@@ -166,11 +186,10 @@ export function blunRuntimeContext(context) {
|
|
|
166
186
|
|
|
167
187
|
export function blunRuntimeMessage(context) {
|
|
168
188
|
const runtime = JSON.parse(blunRuntimeContext(context));
|
|
169
|
-
const warning = runtime.sourceResolution?.incomplete ? ` Warning: ${runtime.sourceResolution.warning}` : "";
|
|
170
189
|
const base = runtime.loaded
|
|
171
190
|
? runtime.preAnswerRecall
|
|
172
|
-
? `AgentSpine ready: ${runtime.indexedSources} sources indexed
|
|
173
|
-
: `AgentSpine ready: ${runtime.indexedSources} sources indexed. Load detailed continuity only on demand through session_briefing
|
|
191
|
+
? `AgentSpine ready: ${runtime.indexedSources} sources indexed.`
|
|
192
|
+
: `AgentSpine ready: ${runtime.indexedSources} sources indexed. Load detailed continuity only on demand through session_briefing.`
|
|
174
193
|
: `AgentSpine unavailable${runtime.sourceResolution?.reason ? `: ${runtime.sourceResolution.reason}` : ""}. ${runtime.instruction}`;
|
|
175
194
|
const active = {};
|
|
176
195
|
if (runtime.signal && (runtime.signal.captured || runtime.signal.accepted
|
|
@@ -195,22 +214,25 @@ export function blunRuntimeMessage(context) {
|
|
|
195
214
|
: runtime.premortem?.instruction;
|
|
196
215
|
const premortem = instruction ? `\n${instruction}` : "";
|
|
197
216
|
const message = `${base}${recall}${details}${premortem}`;
|
|
198
|
-
if (Buffer.byteLength(message) <=
|
|
199
|
-
const compact = `${base}${recall}${
|
|
200
|
-
if (Buffer.byteLength(compact) <=
|
|
217
|
+
if (Buffer.byteLength(message) <= MAX_BYTES) return message;
|
|
218
|
+
const compact = `${base}${recall}${BOUND}\n${compactPremortemRegistration(runtime.premortem)}`;
|
|
219
|
+
if (Buffer.byteLength(compact) <= MAX_BYTES) return compact;
|
|
201
220
|
if (!runtime.preAnswerRecall) {
|
|
202
|
-
return `${base}${
|
|
221
|
+
return `${base}${BOUND}\n${compactPremortemRegistration(runtime.premortem, false)}`;
|
|
203
222
|
}
|
|
204
|
-
const recallOnly = `${base}${recall}${
|
|
205
|
-
if (Buffer.byteLength(recallOnly) <=
|
|
206
|
-
return Buffer.byteLength(recall)<=
|
|
223
|
+
const recallOnly = `${base}${recall}${BOUND}`;
|
|
224
|
+
if (Buffer.byteLength(recallOnly) <= MAX_BYTES) return recallOnly;
|
|
225
|
+
return Buffer.byteLength(recall)<=MAX_BYTES+1?recall.slice(1):`${base}\nRecall unavailable.`;
|
|
207
226
|
}
|
|
208
227
|
|
|
209
|
-
export function hookOutput(event, context, env = process.env) {
|
|
228
|
+
export function hookOutput(event, context, env = process.env, sourceWarning = null) {
|
|
210
229
|
if (env.BLUN_PLUGIN_ROOT) {
|
|
211
|
-
|
|
230
|
+
const source=JSON.parse(context).sourceResolution,w=sourceWarning||(source?.incomplete&&source.warning);
|
|
231
|
+
return {hookSpecificOutput:{hookEventName:event,additionalContext:blunRuntimeMessage(context),
|
|
232
|
+
...(w?{message:`AgentSpine source warning: ${w}`}:{})}};
|
|
212
233
|
}
|
|
213
|
-
return { hookSpecificOutput: { hookEventName: event, additionalContext: context
|
|
234
|
+
return { hookSpecificOutput: { hookEventName: event, additionalContext: context,
|
|
235
|
+
...(sourceWarning ? { message: sourceWarning } : {}) } };
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
export function blockedHookOutput(event, reason, env = process.env) {
|
|
@@ -234,15 +256,12 @@ export function blockStop(event, reason) {
|
|
|
234
256
|
process.stdout.write(`${JSON.stringify(blockedHookOutput(event, reason))}\n`);
|
|
235
257
|
}
|
|
236
258
|
|
|
237
|
-
export function lifecycleOutput(event, artifactGuard, premortem,
|
|
259
|
+
export function lifecycleOutput(event, artifactGuard, premortem, delivery = null, env = process.env,
|
|
238
260
|
sourceWarning = null, lessonRecall = null) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
if (deliveryVerification?.status === "test-failed" && deliveryVerification.reason) {
|
|
242
|
-
messages.push(deliveryVerification.reason);
|
|
243
|
-
}
|
|
261
|
+
const w=[sourceWarning&&`AgentSpine source warning: ${sourceWarning}`,artifactGuard?.reason,
|
|
262
|
+
delivery?.status==="test-failed"&&delivery.reason].filter(Boolean),c=[];
|
|
244
263
|
if (/^[a-f0-9]{64}$/.test(premortem?.writeDigest || "")) {
|
|
245
|
-
|
|
264
|
+
c.push([
|
|
246
265
|
premortem.writeIntent
|
|
247
266
|
? "AgentSpine recorded the allowed mutation intent for the delivery premortem."
|
|
248
267
|
: "AgentSpine recorded the direct write for the delivery premortem.",
|
|
@@ -250,7 +269,7 @@ export function lifecycleOutput(event, artifactGuard, premortem, deliveryVerific
|
|
|
250
269
|
].join("\n"));
|
|
251
270
|
}
|
|
252
271
|
if (lessonRecall?.status === "recalled") {
|
|
253
|
-
|
|
272
|
+
c.push(JSON.stringify({
|
|
254
273
|
schema: lessonRecall.schema, receiptDigest: lessonRecall.receiptDigest,
|
|
255
274
|
instruction: lessonRecall.instruction, items: lessonRecall.items,
|
|
256
275
|
learning: lessonRecall.learning || [], learningDiagnostics: lessonRecall.learningDiagnostics || null,
|
|
@@ -258,6 +277,8 @@ export function lifecycleOutput(event, artifactGuard, premortem, deliveryVerific
|
|
|
258
277
|
authority: "context-only"
|
|
259
278
|
}));
|
|
260
279
|
}
|
|
261
|
-
|
|
262
|
-
|
|
280
|
+
const o={hookEventName:event},f=env.BLUN_PLUGIN_ROOT?"message":"additionalContext";
|
|
281
|
+
if(w.length)o[f]=w.join("\n");
|
|
282
|
+
if(c.length)o.additionalContext=[o.additionalContext,...c].filter(Boolean).join("\n");
|
|
283
|
+
return Object.keys(o).length>1?{hookSpecificOutput:o}:{};
|
|
263
284
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { sessionTimelineLifecycleHint } from "./session-timeline.js";
|
|
2
2
|
import { issueHostTranscriptReceipt } from "./session-timeline-enrollment.js";
|
|
3
3
|
import { consumeTimelineHostOrigin } from "./session-timeline-host-origin.js";
|
|
4
|
+
import {
|
|
5
|
+
automaticPreAnswerTimelineRecall, timelineRecallNotFound
|
|
6
|
+
} from "./pre-answer-timeline-recall.js";
|
|
4
7
|
import {
|
|
5
8
|
timelineHostForRuntime, timelineHostHome, timelineProtocolFromRuntime, timelineSourceFromRuntime
|
|
6
9
|
} from "./session-timeline-provider.js";
|
|
@@ -21,7 +24,6 @@ function hasRawTimelineGroupSignal(input) {
|
|
|
21
24
|
}
|
|
22
25
|
return values.some((value) => value !== undefined && value !== null && value !== "");
|
|
23
26
|
} catch {
|
|
24
|
-
// Parser-shaped uncertainty must not turn a lifecycle hint into a denial.
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -36,8 +38,6 @@ function receiptObservation(receipt) {
|
|
|
36
38
|
expiresAt: receipt.expiresAt || null, authority: AUTHORITY };
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
// This observes a provider hook payload but never returns the
|
|
40
|
-
// opaque enrollment token through model-visible context.
|
|
41
41
|
export async function observeHostTranscriptReceipt({ root, event, input, scope, hostHome, hostOrigin = null, clock = null }) {
|
|
42
42
|
if (event !== "UserPromptSubmit") return receiptObservation({ status: "not-applicable" });
|
|
43
43
|
if (hasRawTimelineGroupSignal(input)) return receiptObservation({ status: "unavailable", reason: "raw-group-scope" });
|
|
@@ -50,7 +50,9 @@ export async function observeHostTranscriptReceipt({ root, event, input, scope,
|
|
|
50
50
|
return receiptObservation(receipt);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export async function captureSessionTimelineLifecycle({
|
|
53
|
+
export async function captureSessionTimelineLifecycle({
|
|
54
|
+
root, event, input, scope, hostHome, hostOrigin = null, clock = null, turnId = null, prompt
|
|
55
|
+
}) {
|
|
54
56
|
if (hasRawTimelineGroupSignal(input)) return groupSuppressed("raw-group-scope");
|
|
55
57
|
if (!scope || scope.groupId !== null) return groupSuppressed("computed-group-scope");
|
|
56
58
|
let hostReceipt = null;
|
|
@@ -64,14 +66,21 @@ export async function captureSessionTimelineLifecycle({ root, event, input, scop
|
|
|
64
66
|
const result = (value) => hostReceipt ? { ...value, hostReceipt } : value;
|
|
65
67
|
const host = timelineHostForRuntime(scope.host, process.env);
|
|
66
68
|
if (!host || (host === "king" && !timelineProtocolFromRuntime(host))) return unavailable("host-not-supported");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
const session = input.session_id ?? input.sessionId;
|
|
70
|
+
const lifecycle = await sessionTimelineLifecycleHint({ root, host,
|
|
71
|
+
sessionId: session, scope, environment: process.env });
|
|
72
|
+
if (event !== "UserPromptSubmit" || !hostOrigin) return result(lifecycle);
|
|
73
|
+
const preAnswerRecall = lifecycle.priorSessions?.available
|
|
74
|
+
? await automaticPreAnswerTimelineRecall({
|
|
75
|
+
root, host, sessionId: session, scope, hostHome, eventId: input.event_id ?? input.hook_event_id,
|
|
76
|
+
turnId, prompt,
|
|
77
|
+
environment: process.env
|
|
78
|
+
}) : timelineRecallNotFound();
|
|
79
|
+
const prior = lifecycle.priorSessions;
|
|
80
|
+
return result({ ...lifecycle, priorSessions: prior && { available: prior.available, sessions: prior.sessions,
|
|
81
|
+
indexedEvents: prior.indexedEvents, freshness: prior.freshness, authority: prior.authority }, preAnswerRecall });
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
// The calling hook performs this only at its final pre-answer boundary. The
|
|
72
|
-
// origin helper consumes the exact signed preflight receipt before this can
|
|
73
|
-
// touch timeline receipt state. Failure to form a timeline origin is optional
|
|
74
|
-
// and yields no opaque receipt; failure to consume preflight remains blocking.
|
|
75
84
|
export async function finalizeUserPromptSessionTimeline({
|
|
76
85
|
root, input, scope, resolvedSources, preflight, prompt, now = new Date()
|
|
77
86
|
}) {
|
|
@@ -83,7 +92,7 @@ export async function finalizeUserPromptSessionTimeline({
|
|
|
83
92
|
return { preflightConsumed: true, briefingOrigin: consumed.briefingOrigin,
|
|
84
93
|
timeline: await captureSessionTimelineLifecycle({
|
|
85
94
|
root, event: "UserPromptSubmit", input, scope, hostHome: resolvedSources.hostHome,
|
|
86
|
-
hostOrigin: consumed.origin, clock: () => new Date(now)
|
|
95
|
+
hostOrigin: consumed.origin, clock: () => new Date(now), turnId: preflight.receipt.id, prompt
|
|
87
96
|
}) };
|
|
88
97
|
} catch (error) {
|
|
89
98
|
return { preflightConsumed: true, briefingOrigin: consumed.briefingOrigin, timeline: {
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
const
|
|
2
|
-
export const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export function isKingHost(env = process.env) {
|
|
6
|
-
return Boolean(env.BLUN_PLUGIN_ROOT && env.BLUN_HOME);
|
|
7
|
-
}
|
|
1
|
+
const STANDARD = 8192;
|
|
2
|
+
export const KING_SOURCE_BYTES = 4194304;
|
|
3
|
+
const KING_TOTAL = 8388608;
|
|
4
|
+
export const isKingHost = (env = process.env) => !!(env.BLUN_PLUGIN_ROOT && env.BLUN_HOME);
|
|
8
5
|
|
|
9
6
|
export function instructionBudget(host, usedBytes = 0, env = process.env) {
|
|
10
|
-
const hardLimitBytes = host === "codex" && isKingHost(env) ?
|
|
11
|
-
: host === "claude" ?
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
mode: overflowBytes ? `${host}-required-overflow` : "standard",
|
|
15
|
-
standardBytes: STANDARD_BYTES, hardLimitBytes, usedBytes, overflowBytes
|
|
16
|
-
};
|
|
7
|
+
const hardLimitBytes = host === "codex" && isKingHost(env) ? KING_TOTAL
|
|
8
|
+
: host === "claude" ? 16384 : host === "codex" ? 32768 : STANDARD;
|
|
9
|
+
return { mode: usedBytes > STANDARD ? `${host}-required-overflow` : "standard",
|
|
10
|
+
standardBytes: STANDARD, hardLimitBytes, usedBytes, overflowBytes: Math.max(0, usedBytes - STANDARD) };
|
|
17
11
|
}
|
|
@@ -51,9 +51,6 @@ export function timelineInterpretationRequest(value) {
|
|
|
51
51
|
return structuredClone(value);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// Hosts qualify MCP tools differently. These prefixes must stay exact: a
|
|
55
|
-
// foreign MCP server can expose an identically named tool but must never
|
|
56
|
-
// receive a timeline invocation permit.
|
|
57
54
|
export const AGENTSPINE_TIMELINE_TOOL_PREFIX = "mcp__plugin_agent-spine_agent-spine__";
|
|
58
55
|
export const BLUN_TIMELINE_TOOL_PREFIX = "mcp__agent-spine__";
|
|
59
56
|
|
|
@@ -138,10 +135,6 @@ function resolvedClaims(args) {
|
|
|
138
135
|
return result;
|
|
139
136
|
}
|
|
140
137
|
|
|
141
|
-
// Direct stdio callers can bypass the MCP schema. Merge the flat and nested
|
|
142
|
-
// representations only when every present claim agrees. In particular, a
|
|
143
|
-
// nested group claim stays visible when a top-level argument tries to clear
|
|
144
|
-
// it with null, so no existing private permit crosses into a group turn.
|
|
145
138
|
export function timelineInvocationInput(args = {}) {
|
|
146
139
|
return resolvedClaims(args);
|
|
147
140
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {createHash} from "node:crypto";
|
|
2
|
+
import {authorizeSessionTimelineInvocation,searchSessionTimeline} from "./session-timeline.js";
|
|
3
|
+
import {resolvePrivateSessionTimelineEnrollment} from "./session-timeline-enrollment.js";
|
|
4
|
+
import {rankTimelineEvents,timelineTerms} from "./session-timeline-query.js";
|
|
5
|
+
import {timelineTransportDigest} from "./session-timeline-transport.js";
|
|
6
|
+
|
|
7
|
+
const SCHEMA="agentspine.pre-answer-timeline-recall/v1", AUTHORITY="context-only";
|
|
8
|
+
const hash=value=>createHash("sha256").update(value).digest("hex");
|
|
9
|
+
const unavailable=(reason,s=0,o=0)=>({schema:SCHEMA,status:"unavailable",reason,awaited:true,
|
|
10
|
+
sourceReads:s,events:[],omittedEvents:o,completionVerified:false,authority:AUTHORITY});
|
|
11
|
+
|
|
12
|
+
function exactScope(binding,visibility){
|
|
13
|
+
const {host,sessionId,taskId,...continuity}=binding;
|
|
14
|
+
return {...continuity,groupId:null,currentTaskId:taskId,timelineVisibility:visibility};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function prefix(values){
|
|
18
|
+
let value=values[0]||"";
|
|
19
|
+
for (const item of values.slice(1)) while(value&&!item.startsWith(value)) value=value.slice(0,-1);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function compact(events){
|
|
24
|
+
const sources=[],indices=new Map(),messagePrefix=prefix(events.map(item=>item.messageRef)),
|
|
25
|
+
timePrefix=prefix(events.map(item=>item.at));
|
|
26
|
+
const rows=events.map(item=>{
|
|
27
|
+
const key=`${item.sourceDigest}\0${item.sourceProvider}\0${item.sessionRef}`;
|
|
28
|
+
let source=indices.get(key);
|
|
29
|
+
if(source===undefined){source=sources.length;indices.set(key,source);
|
|
30
|
+
sources.push({digest:item.sourceDigest,provider:item.sourceProvider,session:item.sessionRef});}
|
|
31
|
+
const kind=item.kind === "objective-result" ? "result" : item.kind === "user-message-candidate" ? "user" : "correction";
|
|
32
|
+
const value=kind === "result" ? [item.outcome,item.count?.value ?? null,item.count?.total ?? null,
|
|
33
|
+
item.testLabel, item.excerpt] : kind === "correction" ? item.nextStepSummary : item.sourceText;
|
|
34
|
+
return [source, item.messageRef.slice(messagePrefix.length), item.at.slice(timePrefix.length), kind, value];
|
|
35
|
+
});
|
|
36
|
+
const fields = ["source", "message", "at", "kind", "value"], prefixes = [messagePrefix, timePrefix];
|
|
37
|
+
return sources.length === 1 ? { source: sources[0], prefixes, fields: fields.slice(1), events: rows.map((row) => row.slice(1)) }
|
|
38
|
+
: { sources, prefixes, fields, events: rows };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function lane({root,enrollment,hostHome,transport,turnId,name,query,ref,at,environment}){
|
|
42
|
+
const {binding:b,enrollmentDigest:d,timelineVisibility:v}=enrollment,{host,...fields}=b,w=at?900:0;
|
|
43
|
+
const scope = exactScope(b,v);
|
|
44
|
+
const runtime = { root, host, sessionId:b.sessionId, scope, hostHome, environment };
|
|
45
|
+
const request = { root, tool: "search", ...fields, groupId: null,
|
|
46
|
+
timelineVisibility:v, enrollmentDigest:d, at:at||null, query, windowSeconds:w,
|
|
47
|
+
includePriorSessions: true, includePriorProviders: false, ...(ref && { ref }) };
|
|
48
|
+
const authorized = await authorizeSessionTimelineInvocation({ ...runtime, tool: "search", request,
|
|
49
|
+
toolUseId: `automatic-pre-answer:${hash(`${turnId}\0${name}\0${d}`)}`,
|
|
50
|
+
transportDigest: transport, enrollmentDigest:d });
|
|
51
|
+
return authorized && searchSessionTimeline({ ...runtime, at, query, windowSeconds:w, includePriorSessions: true,
|
|
52
|
+
includePriorProviders: false, invocationRequest: request, invocationTool: "search", transportDigest: transport,
|
|
53
|
+
enrollmentDigest:d });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function automaticPreAnswerTimelineRecall({root,host,sessionId,scope,hostHome,eventId,
|
|
57
|
+
turnId,prompt,environment=process.env}) {
|
|
58
|
+
if(!scope||scope.groupId!==null||!scope.currentTaskId)return unavailable("private-task-scope-unavailable");
|
|
59
|
+
let sourceReads=0;
|
|
60
|
+
try {
|
|
61
|
+
const enrollment=await resolvePrivateSessionTimelineEnrollment({root,host,sessionId,hostHome});
|
|
62
|
+
if(enrollment.status!=="enrolled")return unavailable("private-enrollment-unavailable");
|
|
63
|
+
const transport=timelineTransportDigest({root,binding:enrollment.binding,environment});
|
|
64
|
+
if(!transport)return unavailable("timeline-transport-unavailable");
|
|
65
|
+
const recallId=turnId||eventId||"turn",terms=timelineTerms(prompt);
|
|
66
|
+
sourceReads++;
|
|
67
|
+
const objective = await lane({ root, enrollment, hostHome, transport, turnId: recallId,
|
|
68
|
+
name: "objective", query: ["objective result", ...terms].join(" ").slice(0,512), environment });
|
|
69
|
+
if(!objective||objective.blocked)return unavailable("timeline-search-unavailable",sourceReads);
|
|
70
|
+
const selected = rankTimelineEvents((objective.events || []).map((item) =>
|
|
71
|
+
({ ...item, terms: timelineTerms(item.excerpt) })), terms)[0];
|
|
72
|
+
if(!selected)return timelineRecallNotFound(sourceReads);
|
|
73
|
+
sourceReads++;
|
|
74
|
+
const natural = await lane({ root, enrollment, hostHome, transport, turnId: recallId,
|
|
75
|
+
name: "natural-feedback", query: "user message", ref: selected?.sessionRef, at:selected?.at, environment });
|
|
76
|
+
if(!natural||natural.blocked)return unavailable("timeline-search-unavailable",sourceReads);
|
|
77
|
+
const events = [...selected ? [selected] : [], ...(natural.events || []).slice(0, 3)];
|
|
78
|
+
const unique = [...new Map(events.map((item) => [`${item.sourceDigest}\0${item.id}`, item])).values()];
|
|
79
|
+
return { schema: SCHEMA, status: unique.length ? "recalled" : "not-found", awaited: true,
|
|
80
|
+
sourceReads, ...compact(unique), omittedEvents:(objective.events?.length||0)
|
|
81
|
+
+(natural.events?.length||0)-unique.length,completionVerified:false,
|
|
82
|
+
naturalMessages: "unresolved", trust: "untrusted-session-history", authority: AUTHORITY };
|
|
83
|
+
} catch { return unavailable("timeline-search-unavailable", sourceReads); }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const removeLast=r=>r.events.length>1?{...r,events:r.events.slice(0,-1),omittedEvents:r.omittedEvents+1}
|
|
87
|
+
:unavailable("host-context-budget",r.sourceReads,r.omittedEvents+1);
|
|
88
|
+
export const timelineRecallNotFound=(s=0)=>({...unavailable(null,s),status:"not-found"});
|
|
89
|
+
|
|
90
|
+
export function fitTimelineRecallToHostContext({timeline,render,maximumBytes}) {
|
|
91
|
+
let fitted=timeline,recall=timeline?.preAnswerRecall,context=render(fitted);
|
|
92
|
+
while (Buffer.byteLength(context)>maximumBytes&&recall?.events?.length) {
|
|
93
|
+
recall=removeLast(recall); fitted={...fitted,preAnswerRecall:recall}; context=render(fitted);
|
|
94
|
+
}
|
|
95
|
+
if (Buffer.byteLength(context)>maximumBytes&&recall) {
|
|
96
|
+
fitted={...fitted,preAnswerRecall:unavailable("host-context-budget",recall.sourceReads,
|
|
97
|
+
recall.omittedEvents)}; context=render(fitted);
|
|
98
|
+
}
|
|
99
|
+
if (Buffer.byteLength(context)>maximumBytes) {
|
|
100
|
+
fitted={schema:"agentspine.session-timeline/v1",status:"unavailable",
|
|
101
|
+
reason: "host-context-budget", authority: AUTHORITY }; context = render(fitted);
|
|
102
|
+
}
|
|
103
|
+
return {timeline:fitted,context};
|
|
104
|
+
}
|
|
@@ -11,8 +11,6 @@ const RECORDS = new Set(["session_meta", "response_item", "event_msg", "compacte
|
|
|
11
11
|
"token_usage_record", "world_state", "retained_context", "security_risk_score", "realtime_item",
|
|
12
12
|
"inter_agent_communication", "inter_agent_communication_metadata"]);
|
|
13
13
|
|
|
14
|
-
// Read only the explicitly supplied native source. No directory discovery,
|
|
15
|
-
// decompression, inherited-history traversal or source rewriting is allowed.
|
|
16
14
|
export async function validateCodexTimelineHeader({ source, root, sessionId }) {
|
|
17
15
|
let handle;
|
|
18
16
|
try {
|
|
@@ -37,8 +35,6 @@ export async function validateCodexTimelineHeader({ source, root, sessionId }) {
|
|
|
37
35
|
finally { await handle?.close(); }
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
// Whitelist actual Responses output records, never assistant/user assertions.
|
|
41
|
-
// The caller applies the common secret/injection filter to the original line.
|
|
42
38
|
export function codexTimelineToolResult(line) {
|
|
43
39
|
if (!RECORDS.has(line?.type) || line.schema_version !== undefined) {
|
|
44
40
|
throw new Error("codex-history-format-mismatch");
|
|
@@ -7,9 +7,6 @@ export const PRIVATE_TIMELINE_PREFIX_BYTES = 4096;
|
|
|
7
7
|
|
|
8
8
|
function digest(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
9
9
|
|
|
10
|
-
// A receipt and enrollment never read a complete host transcript. A small
|
|
11
|
-
// stable prefix makes later replacement detectable while append verification
|
|
12
|
-
// establishes the bounded full commitment separately.
|
|
13
10
|
export async function privateTimelinePrefixDigest(source, bytes = Math.min(source?.size, PRIVATE_TIMELINE_PREFIX_BYTES)) {
|
|
14
11
|
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > PRIVATE_TIMELINE_PREFIX_BYTES) return null;
|
|
15
12
|
let handle;
|
|
@@ -55,10 +55,6 @@ export async function readPrivateEnrollmentState({
|
|
|
55
55
|
if (typeof assertOwned !== "function" || !recoverableForwardCommit(state, head)) {
|
|
56
56
|
throw new Error("private timeline enrollment state replay was rejected");
|
|
57
57
|
}
|
|
58
|
-
// A state replacement can survive a crash before its paired head replacement.
|
|
59
|
-
// Repair is allowed only while holding the owned lock and only for the exact
|
|
60
|
-
// authenticated one-generation successor of the retained head. Re-read both
|
|
61
|
-
// records after writing so a path swap cannot produce usable content.
|
|
62
58
|
const freshState = await readAuthenticatedTimelineState(path, maximumBytes, assertStable);
|
|
63
59
|
const checkedState = await verifySessionTimelineState(validate(JSON.parse(freshState), root.value));
|
|
64
60
|
const checkedHead = await readHead(headPath, root, assertStable, headSchema, headAuthority, maximumBytes);
|
|
@@ -2,8 +2,6 @@ import { sameTimelineBinding } from "./session-timeline-contract.js";
|
|
|
2
2
|
import { resolvePrivateSessionTimelineEnrollment } from "./session-timeline-enrollment.js";
|
|
3
3
|
import { validTimelineTransportDigest } from "./session-timeline-transport.js";
|
|
4
4
|
|
|
5
|
-
// Keep the digest inside the signed enrollment record. This verifier returns
|
|
6
|
-
// only a boolean so neither the transport value nor its digest reaches cards.
|
|
7
5
|
export async function timelineTransportEnrollmentMatches({
|
|
8
6
|
root, binding, enrollmentDigest, transportDigest, hostHome = null
|
|
9
7
|
}) {
|