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.
Files changed (106) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +13 -3
  3. package/agent-spine-plugin/CHANGELOG.md +1 -1
  4. package/agent-spine-plugin/docs/host-integration.md +1 -15
  5. package/agent-spine-plugin/docs/preflight-recall.md +1 -1
  6. package/agent-spine-plugin/scripts/check-install.js +1 -6
  7. package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
  8. package/agent-spine-plugin/scripts/release-check.js +2 -3
  9. package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
  10. package/agent-spine-plugin/src/hook.js +40 -40
  11. package/agent-spine-plugin/src/lib/briefing.js +6 -3
  12. package/agent-spine-plugin/src/lib/hook-context.js +63 -64
  13. package/agent-spine-plugin/src/lib/hook-output.js +115 -94
  14. package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
  15. package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
  16. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
  17. package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
  18. package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
  19. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
  20. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
  21. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
  22. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
  23. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
  24. package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
  25. package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
  26. package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
  27. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
  28. package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
  29. package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
  30. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
  31. package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
  32. package/agent-spine-plugin/src/lib/source-roots.js +26 -16
  33. package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
  34. package/bin/core-bootstrap.js +18 -10
  35. package/bin/launcher-mode.js +5 -0
  36. package/bin/launcher-runtime.js +27 -0
  37. package/bin/verify-bundled-agent-sources.cjs +28 -2
  38. package/blun.mjs +127 -29
  39. package/bundled-agent-sources.json +192 -155
  40. package/package.json +1 -1
  41. package/standard-skills/translate-native/README.md +1334 -1
  42. package/standard-skills/translate-native/SKILL.md +1 -1
  43. package/standard-skills/translate-native/VERSION +1 -1
  44. package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
  45. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
  46. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
  47. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
  48. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
  49. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
  50. package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
  51. package/standard-skills/translate-native/integrations/website_localization.py +234 -10
  52. package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
  53. package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
  54. package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
  55. package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
  56. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
  57. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
  58. package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
  59. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
  60. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
  61. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
  62. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
  63. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
  64. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
  65. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
  66. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
  67. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
  68. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
  69. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
  70. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
  71. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
  72. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
  73. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
  74. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
  75. package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
  76. package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
  77. package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
  78. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
  79. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
  80. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
  81. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
  82. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
  83. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
  84. package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
  85. package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
  86. package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
  87. package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
  88. package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
  89. package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
  90. package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
  91. package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
  92. package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
  93. package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
  94. package/standard-skills/translate-native/provenance.json +2 -2
  95. package/standard-skills/translate-native/references/commercial-localization.md +96 -17
  96. package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
  97. package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
  98. package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
  99. package/standard-tools/language-guard/VERSION +1 -1
  100. package/standard-tools/language-guard/blun_language_guard.py +3 -1
  101. package/standard-tools/language-guard/check_commercial_review.py +69 -9
  102. package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
  103. package/standard-tools/language-guard/provenance.json +2 -2
  104. package/standard-tools/manifest.json +1 -1
  105. package/worker-host.mjs +115 -22
  106. package/agent-spine-plugin/scripts/check-install-king.js +0 -37
@@ -1,99 +1,119 @@
1
- const BLUN_MESSAGE_MAX_BYTES = 1200;
2
- const BLUN_BOUND_MARKER = "\n[optional runtime detail omitted: 1200-byte bound]";
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 byteSize(value) {
5
- return Buffer.byteLength(JSON.stringify(value));
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 = values[0] || "";
18
- for (const value of values.slice(1)) {
19
- while (prefix && !value.startsWith(prefix)) prefix = prefix.slice(0, -1);
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 >= 4 ? prefix : "";
18
+ return prefix.length>=4?prefix:"";
22
19
  }
23
20
 
24
- function compactCandidateRows(packet, candidates) {
25
- if (candidates.rows.length < 2) return;
26
- if (!candidates.prefixes) {
27
- const digest = candidates.fields.indexOf("digest");
28
- if (digest >= 0 && candidates.rows.every((row) => row[digest] === candidates.rows[0][digest])) {
29
- candidates.common = { ...(candidates.common || {}), digest: candidates.rows[0][digest] };
30
- candidates.fields.splice(digest, 1);
31
- for (const row of candidates.rows) row.splice(digest, 1);
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 = candidates.fields.map((field, index) => {
34
- const values = candidates.rows.map((row) => row[index]);
35
- const prefix = ["id", "message", "session"].includes(field) ? sharedPrefix(values)
36
- : field === "at" && values.every((value) => /^\d{4}-\d{2}-\d{2}T\d{2}:/.test(value))
37
- ? values[0].slice(0, 14) : "";
38
- if (!prefix || !values.every((value) => value.startsWith(prefix))) return "";
39
- for (const row of candidates.rows) row[index] = row[index].slice(prefix.length);
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 (packet.task) {
40
+ if(packet.task){
44
41
  delete packet.task.status;
45
- if (packet.task.lastVerifiedStep) delete packet.task.lastVerifiedStep.result;
42
+ if(packet.task.lastVerifiedStep)delete packet.task.lastVerifiedStep.result;
46
43
  }
47
- const review = packet.feedbackReview;
48
- if (review?.modelClarification) packet.feedbackReview = {
49
- status: review.status, completionVerified: false, target: review.targetAssertionId,
50
- question: review.modelClarification.question, model: true
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 compactFeedbackCandidates(packet) {
55
- const candidates = packet.feedbackCandidates;
56
- const proposal = packet.feedbackReview?.modelClarification;
57
- if (proposal) for (const key of ["completionVerified", "provider", "replaces"]) delete proposal[key];
58
- if (candidates && !Array.isArray(candidates)
59
- && Array.isArray(candidates.fields) && Array.isArray(candidates.rows)) {
60
- compactCandidateRows(packet, candidates);
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 (!Array.isArray(candidates) || candidates.length < 2) return false;
64
- const origins = ["provider", "session"];
65
- const sharedKeys = origins.filter((key) =>
66
- candidates.every((item) => item?.[key] === candidates[0]?.[key]));
67
- const fields = ["id", "text", "digest", "message", "at",
68
- ...origins.filter((key) => !sharedKeys.includes(key))];
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 ? { common: Object.fromEntries(sharedKeys.map((key) =>
72
- [key, candidates[0][key]])) } : {}),
73
- rows: candidates.map((item) => fields.map((key) => item[key]))
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
- compactCandidateRows(packet, packet.feedbackCandidates);
71
+ compactRows(packet,packet.feedbackCandidates);
76
72
  return true;
77
73
  }
78
74
 
79
- export function preAnswerRecallCapsule(detailed) {
80
- if (detailed?.event !== "UserPromptSubmit" || !detailed.loaded) return null;
81
- const briefing = detailed.briefing;
82
- const preflight = detailed.preflight?.briefing;
83
- if (!briefing || !preflight) return null;
84
- if (briefing.scope?.groupId !== null && briefing.scope?.groupId !== undefined) return null;
85
- const omitted = [];
86
- const packet = briefing.preAnswerRecall ? { ...briefing.preAnswerRecall } : {
87
- schema: "agentspine.pre-answer-recall/v1",
88
- order: "review-before-claims-and-actions",
89
- authority: "context-only"
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 (!compactFeedbackCandidates(packet)
96
- || !addRecallField(packet, "mustRemember", mustRemember, BLUN_MESSAGE_MAX_BYTES)) {
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 (!compactFeedbackCandidates(packet)
105
- || !addRecallField(packet, "retrieval", retrieval, BLUN_MESSAGE_MAX_BYTES)) {
124
+ if (!compactFeedback(packet)
125
+ || !addRecallField(packet, "retrieval", retrieval, MAX_BYTES)) {
106
126
  omitted.push("retrieval");
107
127
  }
108
128
  }
109
- compactFeedbackCandidates(packet);
110
- if (omitted.length && byteSize({ ...packet, omitted }) <= BLUN_MESSAGE_MAX_BYTES) packet.omitted = omitted;
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.${warning}`
173
- : `AgentSpine ready: ${runtime.indexedSources} sources indexed. Load detailed continuity only on demand through session_briefing.${warning}`
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) <= BLUN_MESSAGE_MAX_BYTES) return message;
199
- const compact = `${base}${recall}${BLUN_BOUND_MARKER}\n${compactPremortemRegistration(runtime.premortem)}`;
200
- if (Buffer.byteLength(compact) <= BLUN_MESSAGE_MAX_BYTES) return 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}${BLUN_BOUND_MARKER}\n${compactPremortemRegistration(runtime.premortem, false)}`;
221
+ return `${base}${BOUND}\n${compactPremortemRegistration(runtime.premortem, false)}`;
203
222
  }
204
- const recallOnly = `${base}${recall}${BLUN_BOUND_MARKER}`;
205
- if (Buffer.byteLength(recallOnly) <= BLUN_MESSAGE_MAX_BYTES) return recallOnly;
206
- return Buffer.byteLength(recall)<=BLUN_MESSAGE_MAX_BYTES+1?recall.slice(1):`${base}\nRecall unavailable.`;
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
- return { hookSpecificOutput: { hookEventName: event, additionalContext: blunRuntimeMessage(context) } };
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, deliveryVerification = null, env = process.env,
259
+ export function lifecycleOutput(event, artifactGuard, premortem, delivery = null, env = process.env,
238
260
  sourceWarning = null, lessonRecall = null) {
239
- const messages = sourceWarning ? [`AgentSpine source warning: ${sourceWarning}`] : [];
240
- if (artifactGuard?.reason) messages.push(artifactGuard.reason);
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
- messages.push([
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
- messages.push(JSON.stringify({
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
- if (!messages.length) return {};
262
- return { hookSpecificOutput: { hookEventName: event, additionalContext: messages.join("\n") } };
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({ root, event, input, scope, hostHome, hostOrigin = null, clock = null }) {
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
- return result(await sessionTimelineLifecycleHint({ root, host,
68
- sessionId: input.session_id ?? input.sessionId, scope, environment: process.env }));
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 STANDARD_BYTES = 8 * 1024;
2
- export const KING_SOURCE_MAX_BYTES = 4 * 1024 * 1024;
3
- export const KING_TOTAL_MAX_BYTES = 8 * 1024 * 1024;
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) ? KING_TOTAL_MAX_BYTES
11
- : host === "claude" ? 16 * 1024 : host === "codex" ? 32 * 1024 : STANDARD_BYTES;
12
- const overflowBytes = Math.max(0, usedBytes - STANDARD_BYTES);
13
- return {
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
  }) {