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.
- package/CHANGELOG.md +39 -0
- package/README.md +17 -1
- 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 +798 -143
- 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 +215 -82
- 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
|
-
|
|
6
|
+
return createHash("sha256").update(value).digest("hex");
|
|
9
7
|
}
|
|
10
|
-
|
|
11
8
|
function sameContinuationScope(candidate, current, includePriorProviders) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
22
|
+
return state.sources.filter((source) => sameContinuationScope(source.binding, scoped, includePriorProviders))
|
|
23
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
29
24
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|