blun-king-cli 9.1.597 → 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 +39 -0
  2. package/README.md +17 -1
  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 +798 -143
  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 +215 -82
  106. package/agent-spine-plugin/scripts/check-install-king.js +0 -37
@@ -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
  }) {
@@ -177,10 +177,6 @@ async function saveState(state, path, headPath, root, assertOwned, assertStable)
177
177
  });
178
178
  }
179
179
 
180
- // The normal path remains read-only. If a crash left one verified state
181
- // generation ahead of its signed head, retry under the existing owned lock so
182
- // storage can perform its exact one-step repair; every other failure remains
183
- // unavailable and yields no enrollment or source data.
184
180
  async function loadStateWithRecovery(rootPath, { create = false } = {}) {
185
181
  const names = await enrollmentPaths(rootPath, { create });
186
182
  try {
@@ -212,9 +208,6 @@ async function canonicalRoot(root) {
212
208
  catch { return null; }
213
209
  }
214
210
 
215
- // This is called only by a verified provider UserPromptSubmit lifecycle adapter. It
216
- // records a signed, short-lived observation; the opaque token itself is never
217
- // returned through hook context or a briefing.
218
211
  export async function issueHostTranscriptReceipt({
219
212
  root, host, sessionId, scope, transcriptPath, hostHome, event, eventId = null,
220
213
  hostOrigin = null, clock = null, environment = process.env
@@ -241,8 +234,6 @@ export async function issueHostTranscriptReceipt({
241
234
  protocolVersion: timelineProtocolFromRuntime(host, environment) })) {
242
235
  return hostReceiptUnavailable("king-history-format-version-or-binding-mismatch");
243
236
  }
244
- // This bounded 4 KiB digest is an immutable source binding, not transcript
245
- // capture: receipt issuance never retains or injects the source text.
246
237
  const snapshot = await normalizePrivateTimelineSource(source);
247
238
  if (!snapshot) return hostReceiptUnavailable("transcript-snapshot-unavailable");
248
239
  try {
@@ -270,9 +261,6 @@ export async function issueHostTranscriptReceipt({
270
261
  }
271
262
  }
272
263
 
273
- // The local owner can retrieve only the opaque current-session token. The
274
- // protected transport capability proves the caller's host session; no source
275
- // path, scope, or transcript content is returned to a generic CLI process.
276
264
  export async function currentHostTranscriptReceipt({ root, clock = null, environment = process.env }) {
277
265
  const current = runtimeDate(clock);
278
266
  if (!current) return hostReceiptUnavailable("invalid-host-receipt-time");
@@ -292,9 +280,6 @@ export async function currentHostTranscriptReceipt({ root, clock = null, environ
292
280
  }
293
281
  }
294
282
 
295
- // Internal state-only lookup for bootstrap and lifecycle hints. It reads the
296
- // signed enrollment record but deliberately never opens, stats, or hashes the
297
- // transcript; callers still need the normal resolver before any evidence read.
298
283
  export async function loadPrivateSessionTimelineEnrollment({
299
284
  root, host = null, sessionId = null, scope = null, enrollmentDigest: wantedDigest = null,
300
285
  expectedTransportDigest = null, clock = null
@@ -356,9 +341,6 @@ export async function inspectPrivateSessionTimelineEnrollment({
356
341
  const candidates = state.enrollments.filter((item) => item.binding.host === host && item.binding.sessionId === activeSessionId);
357
342
  const requestedPath = typeof transcriptPath === "string" && transcriptPath
358
343
  ? transcriptPath : candidates.length === 1 ? candidates[0].source.path : null;
359
- // Post-compaction input may omit a host profile root. In that case, the
360
- // sealed root from the one matching enrollment is the only allowed
361
- // fallback; an environment or arbitrary caller path is never consulted.
362
344
  const trustedHome = hostHome === null || hostHome === undefined
363
345
  ? candidates.length === 1 ? candidates[0].source.profileRoot : null
364
346
  : isAbsolute(hostHome) ? hostHome : null;
@@ -399,10 +381,6 @@ export async function mutatePrivateSessionTimelineEnrollment({ root, task }) {
399
381
  }
400
382
  }
401
383
 
402
- // A first state write interrupted before any signed head exists is not
403
- // distinguishable from a deleted head. It is never auto-accepted: a local
404
- // owner may explicitly discard all enrollment state, then obtain a fresh host
405
- // receipt. This function exposes no historic binding, path, or transcript.
406
384
  export async function resetPrivateSessionTimelineEnrollment({ root, confirmation }) {
407
385
  if (confirmation !== LOCAL_TIMELINE_ENROLLMENT_RECOVERY_CONFIRMATION) return false;
408
386
  const rootPath = await canonicalRoot(root);
@@ -38,10 +38,6 @@ function eligibleTimelineOrigin(binding, input) {
38
38
  && typeof transcriptPath === "string" && transcriptPath.length > 0;
39
39
  }
40
40
 
41
- // The opaque object is process-local and recorded in a WeakSet only after the
42
- // exact preflight receipt was atomically consumed for this UserPromptSubmit.
43
- // It is never serialized, persisted, returned to a model, or accepted from a
44
- // raw CLI/API object. A raw issuer can therefore not simulate host origin.
45
41
  export async function consumeTimelineHostOrigin({
46
42
  event, input, scope, resolvedSources, preflight, prompt, environment = process.env, now = new Date()
47
43
  }) {
@@ -1,65 +1,57 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { matchesTimelineEvent, rankTimelineEvents } from "./session-timeline-query.js";
3
-
4
3
  const CONTINUITY_FIELDS = ["entityId", "userId", "tenantId", "projectId", "taskId", "portalRef", "threadRef"];
5
4
  const REFERENCE_FIELDS = ["host", ...CONTINUITY_FIELDS, "sessionId"];
6
-
7
5
  function digest(value) {
8
- return createHash("sha256").update(value).digest("hex");
6
+ return createHash("sha256").update(value).digest("hex");
9
7
  }
10
-
11
8
  function sameContinuationScope(candidate, current, includePriorProviders) {
12
- return candidate.groupId === null && current.groupId === null
13
- && (candidate.host !== current.host || candidate.sessionId !== current.sessionId)
14
- && (includePriorProviders || candidate.host === current.host)
15
- && CONTINUITY_FIELDS.every((field) => (candidate[field] ?? null) === (current[field] ?? null))
16
- && (!candidate.goalId || !current.goalId || candidate.goalId === current.goalId);
9
+ return candidate.groupId === null && current.groupId === null
10
+ && (candidate.host !== current.host || candidate.sessionId !== current.sessionId)
11
+ && (includePriorProviders || candidate.host === current.host)
12
+ && CONTINUITY_FIELDS.every((field) => (candidate[field] ?? null) === (current[field] ?? null))
13
+ && (!candidate.goalId || !current.goalId || candidate.goalId === current.goalId);
17
14
  }
18
-
19
15
  export function timelineSessionReference(binding) {
20
- const fields = binding.portalRef || binding.threadRef ? [...REFERENCE_FIELDS, "portalRef", "threadRef"] : REFERENCE_FIELDS;
21
- const material = fields
22
- .map((field) => `${field}:${binding[field] || ""}`).join("\0");
23
- return `session-ref:${digest(material).slice(0, 32)}`;
16
+ const fields = binding.portalRef || binding.threadRef ? [...REFERENCE_FIELDS, "portalRef", "threadRef"] : REFERENCE_FIELDS;
17
+ const material = fields
18
+ .map((field) => `${field}:${binding[field] || ""}`).join("\0");
19
+ return `session-ref:${digest(material).slice(0, 32)}`;
24
20
  }
25
-
26
21
  export function priorTimelineSources(state, scoped, { includePriorProviders = false } = {}) {
27
- return state.sources.filter((source) => sameContinuationScope(source.binding, scoped, includePriorProviders))
28
- .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
22
+ return state.sources.filter((source) => sameContinuationScope(source.binding, scoped, includePriorProviders))
23
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
29
24
  }
30
-
31
- export function selectPriorTimelineSource(state, scoped, { target, wanted, windowMs, includePriorProviders = false }) {
32
- const eligible = priorTimelineSources(state, scoped, { includePriorProviders });
33
- const indexed = eligible.flatMap((source, sourceIndex) => rankTimelineEvents(source.events
34
- .filter((event) => matchesTimelineEvent(event, wanted, target, windowMs)), wanted, target)
35
- .slice(0, 8).map((event) => ({ ...event, sourceIndex })));
36
- const ranked = rankTimelineEvents(indexed, wanted, target);
37
- return eligible[ranked[0]?.sourceIndex ?? 0] || null;
25
+ export function selectPriorTimelineSource(state, scoped, { target, wanted, windowMs, includePriorProviders = false, sessionRef = null }) {
26
+ const eligible = priorTimelineSources(state, scoped, { includePriorProviders })
27
+ .filter((source) => !sessionRef || timelineSessionReference(source.binding) === sessionRef);
28
+ const indexed = eligible.flatMap((source, sourceIndex) => rankTimelineEvents(source.events
29
+ .filter((event) => matchesTimelineEvent(event, wanted, target, windowMs)), wanted, target)
30
+ .slice(0, 8).map((event) => ({ ...event, sourceIndex })));
31
+ const ranked = rankTimelineEvents(indexed, wanted, target);
32
+ return eligible[ranked[0]?.sourceIndex ?? 0] || null;
38
33
  }
39
-
40
34
  export function priorTimelineHint(state, scoped, sourceDigest, { includePriorProviders = false } = {}) {
41
- const sources = priorTimelineSources(state, scoped, { includePriorProviders });
42
- const latest = sources.flatMap((source) => source.events.map((event) => ({ source, event })))
43
- .sort((left, right) => right.event.at.localeCompare(left.event.at))[0] || null;
44
- return {
45
- available: sources.length > 0,
46
- sessions: sources.length,
47
- indexedEvents: sources.reduce((total, source) => total + source.events.length, 0),
48
- latest: latest ? {
49
- sessionRef: timelineSessionReference(latest.source.binding),
50
- messageRef: latest.event.id,
51
- sourceProvider: latest.source.binding.host,
52
- ...(latest.source.binding.portalRef && latest.source.binding.threadRef
53
- ? { portalRef: latest.source.binding.portalRef, threadRef: latest.source.binding.threadRef } : {}),
54
- sourceDigest: sourceDigest(latest.source),
55
- at: latest.event.at,
56
- outcome: latest.event.outcome,
57
- testLabel: latest.event.testLabel
58
- } : null,
59
- instruction: sources.length
60
- ? `Use session_timeline_search with includePriorSessions${includePriorProviders ? " and includePriorProviders" : ""} only for a relevant exact-time or two-term question.`
61
- : null,
62
- freshness: "source-not-read",
63
- authority: "context-only"
64
- };
35
+ const sources = priorTimelineSources(state, scoped, { includePriorProviders });
36
+ const latest = sources.flatMap((source) => source.events.map((event) => ({ source, event })))
37
+ .sort((left, right) => right.event.at.localeCompare(left.event.at))[0] || null;
38
+ return {
39
+ available: sources.length > 0,
40
+ sessions: sources.length,
41
+ indexedEvents: sources.reduce((total, source) => total + source.events.length, 0),
42
+ latest: latest ? {
43
+ sessionRef: timelineSessionReference(latest.source.binding),
44
+ messageRef: latest.event.id,
45
+ sourceProvider: latest.source.binding.host,
46
+ ...(latest.source.binding.portalRef && latest.source.binding.threadRef
47
+ ? { portalRef: latest.source.binding.portalRef, threadRef: latest.source.binding.threadRef } : {}),
48
+ sourceDigest: sourceDigest(latest.source),
49
+ at: latest.event.at,
50
+ outcome: latest.event.outcome,
51
+ testLabel: latest.event.testLabel
52
+ } : null,
53
+ instruction: null,
54
+ freshness: "source-not-read",
55
+ authority: "context-only"
56
+ };
65
57
  }
@@ -11,9 +11,6 @@ export function crossProviderTimelineEnabled(environment = process.env) {
11
11
  return environment?.[TIMELINE_CROSS_PROVIDER_ENV] === "1";
12
12
  }
13
13
 
14
- // BLUN currently shares the Codex-compatible AGENTS.md hierarchy, but its
15
- // persisted agent wire is a distinct provider contract. Keep those identities
16
- // separate so a passing Codex adapter can never stand in for King history.
17
14
  export function timelineHostForRuntime(host, environment = process.env) {
18
15
  if (environment?.BLUN_HOME || environment?.BLUN_PLUGIN_ROOT) return "king";
19
16
  return validTimelineHost(host) ? host : null;
@@ -33,7 +33,6 @@ export function timelineQuery({ at, query }) {
33
33
  }
34
34
 
35
35
  export function matchesTimelineEvent(event, wanted, target, windowMs) {
36
- // Explicit query lane, not a language recognizer. Ordinary searches remain objective/strict-correction only.
37
36
  if (event.kind === "user-message-candidate" && !(wanted.includes("user") && wanted.includes("message"))) return false;
38
37
  return (!target || Math.abs(new Date(event.at).getTime() - target.getTime()) <= windowMs)
39
38
  && (!wanted.length || wanted.filter((term) => event.terms.includes(term)).length >= 2);
@@ -4,8 +4,6 @@ import { lstat, mkdtemp, open, rm } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
 
7
- // Numeric FileSystemRights values and SDDL tokens are stable across Windows
8
- // display languages. Friendly account names never enter the authorization path.
9
7
  const FULL_CONTROL = 0x001f01ff;
10
8
  const WRITE_MASK = 0x000d0156;
11
9
  const GENERIC_WRITE_MASK = 0x50000000;
@@ -168,8 +166,6 @@ async function readNativeWindowsAcl(path, env, runCommand, run, identity) {
168
166
  }
169
167
  }
170
168
 
171
- // Retained only for dependency-injected compatibility tests. Production uses
172
- // icacls /save and parses its SID/SDDL export without starting PowerShell.
173
169
  export function windowsSidAclCommand(path) {
174
170
  const encodedPath = Buffer.from(path, "utf8").toString("base64");
175
171
  const script = [
@@ -71,9 +71,6 @@ export function matchesSourceMetadata(metadata, source) {
71
71
  }
72
72
 
73
73
  export async function pathMatchesSource(source, hostHome = null) {
74
- // A post-compaction read may not have a fresh host root. The persisted
75
- // enrollment root is the sealed authority in that case; ambient process
76
- // configuration must never redirect a previously bound transcript.
77
74
  const trustedHome = isAbsolute(hostHome || "") ? hostHome : source?.profileRoot;
78
75
  if (!isAbsolute(trustedHome || "")) return false;
79
76
  const current = await sourcePath(source.path, trustedHome, source.binding?.host || "claude");
@@ -2,8 +2,6 @@ import { createHash, timingSafeEqual } from "node:crypto";
2
2
  import { completeTimelineBinding, safeTimelineId } from "./session-timeline-contract.js";
3
3
  import { sessionTimelineRootDigest } from "./session-timeline-root.js";
4
4
 
5
- // This capability is deliberately supplied only by a locally configured host
6
- // transport. It is never a MCP argument, stored value, hook output, or card.
7
5
  export const TIMELINE_TRANSPORT_CAPABILITY_ENV = "AGENTSPINE_TIMELINE_SESSION_CAPABILITY";
8
6
  export const TIMELINE_TRANSPORT_SESSION_ENV = "AGENTSPINE_TIMELINE_TRANSPORT_SESSION_ID";
9
7
 
@@ -27,9 +25,6 @@ function capability(environment) {
27
25
  return typeof value === "string" && CAPABILITY_RE.test(value) && sessionId ? { value, sessionId } : null;
28
26
  }
29
27
 
30
- // The environment must carry a freshly generated 32-byte capability for the
31
- // exact host session. A global or argument-supplied fallback is intentionally
32
- // absent: standard MCP stdio does not authenticate callers by itself.
33
28
  export function timelineTransportDigest({ root, binding, environment = process.env }) {
34
29
  const current = capability(environment);
35
30
  if (typeof root !== "string" || !root || !completeTimelineBinding(binding) || !current
@@ -45,6 +40,3 @@ export function sameTimelineTransportDigest(left, right) {
45
40
  if (!validTimelineTransportDigest(left) || !validTimelineTransportDigest(right)) return false;
46
41
  return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
47
42
  }
48
-
49
- // The one-use record is valid only while the exact local enrollment that
50
- // produced it is still active. This check occurs before the record is removed.
@@ -119,9 +119,6 @@ export function createWindowsTimelineAclVerifier({ platform = process.platform,
119
119
  };
120
120
  }
121
121
 
122
- // Every boundary supplies fresh file metadata. On Windows, changing a security
123
- // descriptor changes ctime, so only an identical file identity/change-time can
124
- // reuse a parsed ACL. A changed DACL therefore misses this cache and is denied.
125
122
  export function createWindowsTimelineFileAclVerifier({ platform = process.platform, env = process.env, run = spawnSync } = {}) {
126
123
  const cache = new Map();
127
124
  let identity = null;