trajectories 0.0.0 → 0.1.1

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 (204) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +201 -0
  3. package/README.md +274 -2
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +8542 -0
  6. package/dist/convert.d.ts +31 -0
  7. package/dist/convert.js +431 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +8402 -0
  10. package/dist/log-baseline.d.ts +26 -0
  11. package/dist/log-baseline.js +104 -0
  12. package/dist/log-content.d.ts +16 -0
  13. package/dist/log-content.js +80 -0
  14. package/dist/log-io.d.ts +10 -0
  15. package/dist/log-io.js +121 -0
  16. package/dist/log-read.d.ts +24 -0
  17. package/dist/log-read.js +117 -0
  18. package/dist/log-reconcile.d.ts +5 -0
  19. package/dist/log-reconcile.js +38 -0
  20. package/dist/log-records.d.ts +35 -0
  21. package/dist/log-records.js +85 -0
  22. package/dist/log-recovery.d.ts +18 -0
  23. package/dist/log-recovery.js +123 -0
  24. package/dist/log-stage.d.ts +14 -0
  25. package/dist/log-stage.js +138 -0
  26. package/dist/log-state.d.ts +89 -0
  27. package/dist/log-state.js +92 -0
  28. package/dist/log-switch.d.ts +3 -0
  29. package/dist/log-switch.js +98 -0
  30. package/dist/log-types.d.ts +129 -0
  31. package/dist/log-types.js +8 -0
  32. package/dist/log.d.ts +8 -0
  33. package/dist/log.js +62 -0
  34. package/dist/migrate.d.ts +29 -0
  35. package/dist/migrate.js +173 -0
  36. package/dist/normalization-runtime.d.ts +1 -0
  37. package/dist/normalization-runtime.js +3150 -0
  38. package/dist/portable.d.ts +9 -0
  39. package/dist/portable.js +26 -0
  40. package/dist/readers/canonical.d.ts +13 -0
  41. package/dist/readers/canonical.js +54 -0
  42. package/dist/readers/cline.d.ts +10 -0
  43. package/dist/readers/cline.js +107 -0
  44. package/dist/readers/cursor.d.ts +20 -0
  45. package/dist/readers/cursor.js +374 -0
  46. package/dist/readers/deepagents.d.ts +41 -0
  47. package/dist/readers/deepagents.js +163 -0
  48. package/dist/readers/fx.d.ts +11 -0
  49. package/dist/readers/fx.js +573 -0
  50. package/dist/readers/github-copilot.d.ts +8 -0
  51. package/dist/readers/github-copilot.js +216 -0
  52. package/dist/readers/grok-build.d.ts +12 -0
  53. package/dist/readers/grok-build.js +391 -0
  54. package/dist/readers/guards.d.ts +4 -0
  55. package/dist/readers/guards.js +121 -0
  56. package/dist/readers/pi-compaction.d.ts +7 -0
  57. package/dist/readers/pi-compaction.js +112 -0
  58. package/dist/readers/pi-context.d.ts +13 -0
  59. package/dist/readers/pi-context.js +64 -0
  60. package/dist/store.d.ts +25 -0
  61. package/dist/store.js +218 -0
  62. package/dist/types.d.ts +91 -0
  63. package/dist/types.js +1 -0
  64. package/dist/writers/claude-code.d.ts +5 -0
  65. package/dist/writers/claude-code.js +51 -0
  66. package/dist/writers/cline.d.ts +3 -0
  67. package/dist/writers/cline.js +77 -0
  68. package/dist/writers/codex.d.ts +3 -0
  69. package/dist/writers/codex.js +32 -0
  70. package/dist/writers/cursor.d.ts +3 -0
  71. package/dist/writers/cursor.js +197 -0
  72. package/dist/writers/deepagents.d.ts +3 -0
  73. package/dist/writers/deepagents.js +116 -0
  74. package/dist/writers/entropy.d.ts +5 -0
  75. package/dist/writers/entropy.js +33 -0
  76. package/dist/writers/fx.d.ts +3 -0
  77. package/dist/writers/fx.js +135 -0
  78. package/dist/writers/github-copilot.d.ts +3 -0
  79. package/dist/writers/github-copilot.js +71 -0
  80. package/dist/writers/grok-build.d.ts +3 -0
  81. package/dist/writers/grok-build.js +43 -0
  82. package/dist/writers/helpers.d.ts +17 -0
  83. package/dist/writers/helpers.js +75 -0
  84. package/dist/writers/omp.d.ts +3 -0
  85. package/dist/writers/omp.js +7 -0
  86. package/dist/writers/opencode.d.ts +3 -0
  87. package/dist/writers/opencode.js +65 -0
  88. package/dist/writers/pi.d.ts +3 -0
  89. package/dist/writers/pi.js +45 -0
  90. package/docs/cline-native-format.md +43 -0
  91. package/docs/copilot-native-format.md +78 -0
  92. package/docs/cursor-native-format.md +103 -0
  93. package/docs/deepagents-native-format.md +98 -0
  94. package/docs/evidence-api.md +170 -0
  95. package/docs/fx-native-format.md +87 -0
  96. package/docs/harness-parity-bootstrap.md +78 -0
  97. package/docs/harness-support-parity.md +107 -0
  98. package/docs/inspirations.md +103 -0
  99. package/docs/ledger-migration.md +141 -0
  100. package/docs/pi-compaction-context.md +75 -0
  101. package/docs/pi-provider-context.md +76 -0
  102. package/docs/research/2026-10-06-huihua-and-oar.md +176 -0
  103. package/docs/research/ledger/README.md +96 -0
  104. package/docs/research/ledger/confer/2026-09-13-round-10/README.md +61 -0
  105. package/docs/research/ledger/confer/2026-09-13-round-2/cass.md +72 -0
  106. package/docs/research/ledger/confer/2026-09-13-round-3/README.md +70 -0
  107. package/docs/research/ledger/confer/2026-09-13-round-5/README.md +61 -0
  108. package/docs/research/ledger/confer/2026-09-13-round-6/README.md +68 -0
  109. package/docs/research/ledger/confer/2026-09-13-round-7/README.md +64 -0
  110. package/docs/research/ledger/confer/2026-09-13-round-9/README.md +59 -0
  111. package/docs/research/ledger/confer/2026-09-14-wake/wake.md +146 -0
  112. package/docs/research/ledger/docs/inspirations.md +320 -0
  113. package/docs/research/ledger/docs/pr2-pr6-integration.md +53 -0
  114. package/docs/research/ledger/docs/research/2026-09-07-confer-inspirations.md +163 -0
  115. package/docs/research/ledger/docs/research/2026-09-07-letta-continuous-service.md +104 -0
  116. package/docs/research/ledger/docs/research/2026-09-08-claude-history-retrieval.md +117 -0
  117. package/docs/research/ledger/docs/research/2026-09-09-agentsview-native-discovery.md +87 -0
  118. package/docs/research/ledger/docs/research/2026-09-09-daemon-observation.md +231 -0
  119. package/docs/research/ledger/docs/research/2026-09-09-discovery-pagination.md +114 -0
  120. package/docs/research/ledger/docs/research/2026-09-09-harness-transcript-contract.md +145 -0
  121. package/docs/research/ledger/docs/research/2026-09-09-native-history-discovery.md +180 -0
  122. package/docs/research/ledger/docs/research/2026-09-09-native-history-implementation.md +137 -0
  123. package/docs/research/ledger/docs/research/2026-09-09-other-inspirations.md +279 -0
  124. package/docs/research/ledger/docs/research/2026-09-09-trajectory-and-letta-code.md +128 -0
  125. package/docs/research/ledger/docs/research/2026-09-13-diagnostics-coverage.md +116 -0
  126. package/docs/research/ledger/docs/research/2026-09-13-fts-rebuild.md +83 -0
  127. package/docs/research/ledger/docs/research/2026-09-13-read-path-split.md +74 -0
  128. package/docs/research/ledger/docs/research/2026-09-13-runtime-rollout.md +102 -0
  129. package/docs/research/ledger/docs/research/2026-09-13-scan-outcomes.md +80 -0
  130. package/docs/research/ledger/docs/research/2026-09-13-search-quality.md +105 -0
  131. package/docs/research/ledger/docs/research/2026-09-13-source-coverage.md +64 -0
  132. package/docs/research/ledger/docs/research/2026-09-13-sync-wall-budget.md +82 -0
  133. package/docs/research/ledger/docs/research/2026-09-13-typed-read-gate.md +81 -0
  134. package/docs/research/ledger/docs/research/2026-09-13-writer-lease.md +84 -0
  135. package/docs/research/ledger/docs/research/2026-09-16-c1-copy-fallback.md +227 -0
  136. package/docs/research/ledger/docs/research/2026-09-16-c10-kimi.md +414 -0
  137. package/docs/research/ledger/docs/research/2026-09-16-c2-derive-counter.md +216 -0
  138. package/docs/research/ledger/docs/research/2026-09-16-c3-echo-filter.md +305 -0
  139. package/docs/research/ledger/docs/research/2026-09-16-c4-title-index.md +349 -0
  140. package/docs/research/ledger/docs/research/2026-09-16-c5-trigram.md +268 -0
  141. package/docs/research/ledger/docs/research/2026-09-16-c6-recency-prior.md +438 -0
  142. package/docs/research/ledger/docs/research/2026-09-16-c7-tombstones.md +467 -0
  143. package/docs/research/ledger/docs/research/2026-09-16-c8-remote-truth.md +390 -0
  144. package/docs/research/ledger/docs/research/2026-09-16-c9-grok.md +380 -0
  145. package/docs/research/ledger/docs/research/2026-09-16-wake-port-program.md +38 -0
  146. package/docs/research/ledger/docs/research/2026-09-17-cursor-blobstore.md +157 -0
  147. package/docs/research/ledger/manifest.json +540 -0
  148. package/legacy/NOTICE.md +50 -0
  149. package/legacy/bin/ledger-core.ts +7 -0
  150. package/legacy/bin/ledger.ts +7 -0
  151. package/legacy/bin/traj-bridge.ts +7 -0
  152. package/legacy/scripts/import-claude-history.ts +130 -0
  153. package/legacy/scripts/native-history-smoke.ts +52 -0
  154. package/legacy/scripts/observe-daemon.ts +570 -0
  155. package/legacy/scripts/retitle-sessions.md +19 -0
  156. package/legacy/scripts/verify-runtime-rollout.ts +151 -0
  157. package/legacy/src/canonical.ts +108 -0
  158. package/legacy/src/cli.ts +1293 -0
  159. package/legacy/src/db.ts +2254 -0
  160. package/legacy/src/diagnostics.ts +87 -0
  161. package/legacy/src/discovery.ts +56 -0
  162. package/legacy/src/embeddings.ts +382 -0
  163. package/legacy/src/filter.ts +602 -0
  164. package/legacy/src/gemini_embeddings.ts +103 -0
  165. package/legacy/src/identity.ts +9 -0
  166. package/legacy/src/index.ts +10 -0
  167. package/legacy/src/ingest.ts +791 -0
  168. package/legacy/src/managed.ts +178 -0
  169. package/legacy/src/native/common.ts +68 -0
  170. package/legacy/src/native/cursor.ts +647 -0
  171. package/legacy/src/native/grok.ts +540 -0
  172. package/legacy/src/native/kimi.ts +706 -0
  173. package/legacy/src/native/opencode.ts +470 -0
  174. package/legacy/src/native/sqlite-ro.ts +281 -0
  175. package/legacy/src/native.ts +97 -0
  176. package/legacy/src/normalizer.ts +457 -0
  177. package/legacy/src/parentage.ts +562 -0
  178. package/legacy/src/pressure.ts +162 -0
  179. package/legacy/src/private-storage.ts +74 -0
  180. package/legacy/src/reader.ts +1255 -0
  181. package/legacy/src/scanner.ts +591 -0
  182. package/legacy/src/schema-gate.ts +263 -0
  183. package/legacy/src/schema.ts +829 -0
  184. package/legacy/src/semantic-top-k.ts +71 -0
  185. package/legacy/src/session-hash.ts +49 -0
  186. package/legacy/src/store-status.ts +218 -0
  187. package/legacy/src/sync-preparation.ts +45 -0
  188. package/legacy/src/sync-truth.ts +843 -0
  189. package/legacy/src/sync.ts +1219 -0
  190. package/legacy/src/types.ts +436 -0
  191. package/legacy/src/vocab.txt +30522 -0
  192. package/legacy/src/watch.ts +673 -0
  193. package/legacy/src/writer-lease.ts +310 -0
  194. package/ops/README.md +120 -0
  195. package/ops/install-cli.sh +63 -0
  196. package/ops/install-release.sh +49 -0
  197. package/ops/linux/install-systemd-user.sh +103 -0
  198. package/ops/macos/install-launchd.sh +72 -0
  199. package/ops/sync-settings.sh +37 -0
  200. package/package.json +19 -5
  201. package/skills/trajectories/SKILL.md +138 -0
  202. package/skills/trajectories/scripts/trajectories_recent.sh +26 -0
  203. package/skills/trajectories/scripts/trajectories_search.sh +26 -0
  204. package/index.js +0 -1
@@ -0,0 +1,85 @@
1
+ import { digest, getObject, putObject, stableJson } from './log-io.js';
2
+ import { TrajectoriesError } from './log-types.js';
3
+ import { portableConversationTokens } from './portable.js';
4
+ import { normalizePiArchive, projectPiDrafts } from './readers/pi-context.js';
5
+ import { getText as loadText, putText as saveText } from './log-content.js';
6
+ export function units(snapshot, archive = false) {
7
+ const records = archive && snapshot.source.harness === 'pi'
8
+ ? projectPiDrafts(snapshot.raw, normalizePiArchive(snapshot.raw)).records : snapshot.canonical.records;
9
+ const result = [];
10
+ for (const canonical of records) {
11
+ const original = JSON.parse(canonical.record_json);
12
+ for (const token of portableConversationTokens([original])) {
13
+ const payload = original.role === 'assistant' && 'tool_calls' in original
14
+ ? { ...original, tool_calls: [original.tool_calls[token.componentIndex]] } : original;
15
+ const record_json = JSON.stringify(payload);
16
+ result.push({ semantic: token.semantic, payload,
17
+ canonical: { ...canonical, record_json, record_hash: digest(record_json) } });
18
+ }
19
+ }
20
+ return result;
21
+ }
22
+ function nativeKey(unit) {
23
+ return stableJson([unit.canonical.stable_source_record_id, unit.canonical.component_index, unit.semantic]);
24
+ }
25
+ export function contextEdit(before, after) {
26
+ let start = 0, end = 0;
27
+ while (start < before.length && start < after.length && before[start] === after[start])
28
+ start++;
29
+ while (end < before.length - start && end < after.length - start && before.at(-end - 1) === after.at(-end - 1))
30
+ end++;
31
+ return { start, remove: before.length - start - end, insert: after.slice(start, after.length - end) };
32
+ }
33
+ export function applyEdit(context, edit) {
34
+ if (!edit)
35
+ return [...context];
36
+ if (!Number.isSafeInteger(edit.start) || !Number.isSafeInteger(edit.remove) || edit.start < 0 || edit.remove < 0
37
+ || edit.start + edit.remove > context.length)
38
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Invalid context edit.');
39
+ return [...context.slice(0, edit.start), ...edit.insert, ...context.slice(edit.start + edit.remove)];
40
+ }
41
+ export async function loadRecord(store, record) {
42
+ const ref = JSON.parse((await getObject(store, record.object)).toString());
43
+ const stored = JSON.parse(await loadText(store, ref));
44
+ const value = { ...stored, payload: JSON.parse(stored.canonical.record_json) };
45
+ if (digest(stored.canonical.record_json) !== stored.canonical.record_hash)
46
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Canonical payload hash mismatch.');
47
+ if (value.id !== record.id)
48
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Canonical record identity mismatch.');
49
+ return value;
50
+ }
51
+ export async function planRecords(input) {
52
+ const archive = units(input.observed, true), mapping = [...input.beforeIds], records = [], added = [];
53
+ async function accept(unit) {
54
+ const id = `r_${digest(stableJson([input.sessionId, input.intentId, records.length]))}`;
55
+ const value = { id, canonical: unit.canonical, origin: input.origin };
56
+ const text = await saveText(input.store, stableJson(value));
57
+ records.push({ id, object: await putObject(input.store, stableJson(text)) });
58
+ return id;
59
+ }
60
+ let leading, offset = 0;
61
+ if (input.allowInitialSystem && input.before[0]?.payload.role !== 'system' && archive[0]?.payload.role === 'system') {
62
+ leading = await accept(archive[0]);
63
+ mapping.unshift(leading);
64
+ offset = 1;
65
+ }
66
+ if (archive.length - offset < input.before.length || input.before.some((unit, i) => unit.semantic !== archive[i + offset]?.semantic))
67
+ throw new TrajectoriesError('UNSUPPORTED_CONTEXT', 'Captured archive does not preserve its latest accepted native prefix.');
68
+ for (let i = input.before.length + offset; i < archive.length; i++) {
69
+ const id = await accept(archive[i]);
70
+ mapping.push(id);
71
+ added.push(id);
72
+ }
73
+ const byNative = new Map();
74
+ for (const [index, unit] of input.beforeActive.entries())
75
+ if (input.beforeActiveIds[index])
76
+ byNative.set(nativeKey(unit), input.beforeActiveIds[index]);
77
+ for (const [index, unit] of archive.entries())
78
+ if (mapping[index])
79
+ byNative.set(nativeKey(unit), mapping[index]);
80
+ const active = [];
81
+ for (const unit of units(input.observed))
82
+ active.push(byNative.get(nativeKey(unit)) ?? await accept(unit));
83
+ return { records, edit: contextEdit(input.context, active), added, ...(leading ? { leading } : {}),
84
+ progress: archive.length - offset > input.before.length };
85
+ }
@@ -0,0 +1,18 @@
1
+ import type { NativeExport, Snapshot } from './types.js';
2
+ import type { BindingId, CaptureRef } from './log-types.js';
3
+ import type { Frame, StageData } from './log-state.js';
4
+ export declare const MANAGED_CODEC_VERSION = "trajectories-native-codec-2";
5
+ export declare const capturePath: (store: string, id: string) => string;
6
+ export declare function loadStage(store: string, ref: CaptureRef): Promise<StageData>;
7
+ interface RecoveryCache {
8
+ snapshots: Map<string, Snapshot>;
9
+ bindings: Map<string, Snapshot>;
10
+ exports: Map<string, NativeExport>;
11
+ visiting: Set<string>;
12
+ }
13
+ export declare function regenerateExport(store: string, frames: Frame[], bindingId: BindingId, cache?: RecoveryCache): Promise<NativeExport>;
14
+ export declare function bindingSnapshot(store: string, frames: Frame[], bindingId: BindingId, cache?: RecoveryCache): Promise<Snapshot>;
15
+ export declare function exportRecords(store: string, frames: Frame[], source: Snapshot): Promise<import('@letta-ai/trajectory').NormalizedRecord[]>;
16
+ export declare function recoverSnapshot(store: string, stage: StageData, frames: Frame[], cache?: RecoveryCache): Promise<Snapshot>;
17
+ export declare function recoverEvidence(store: string, evidence: StageData['evidence'], frames: Frame[], ownId?: string, cache?: RecoveryCache): Promise<Snapshot>;
18
+ export {};
@@ -0,0 +1,123 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { TrajectoriesError } from './log-types.js';
4
+ import { getObject, safeId, stableJson } from './log-io.js';
5
+ import { derivedChunks, getText } from './log-content.js';
6
+ import { atHead, selection } from './log-read.js';
7
+ import { loadRecord } from './log-records.js';
8
+ import { convertManaged, readNativeExportSnapshot, sha256, snapshotFromEvidence } from './convert.js';
9
+ import { NATIVE_ENTROPY_VERSION } from './writers/entropy.js';
10
+ export const MANAGED_CODEC_VERSION = 'trajectories-native-codec-2';
11
+ export const capturePath = (store, id) => join(resolve(store), 'captures', `${safeId(id)}.json`);
12
+ export async function loadStage(store, ref) {
13
+ let pointer;
14
+ try {
15
+ pointer = JSON.parse(await readFile(capturePath(store, ref.id), 'utf8'));
16
+ }
17
+ catch (cause) {
18
+ if (cause.code === 'ENOENT')
19
+ throw new TrajectoriesError('NOT_FOUND', 'The staged capture is missing from this store.', { cause });
20
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'The staged capture pointer is corrupt or unreadable.', { cause });
21
+ }
22
+ let stage;
23
+ try {
24
+ stage = JSON.parse((await getObject(store, pointer.object)).toString());
25
+ }
26
+ catch (cause) {
27
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Invalid staged capture manifest.', { cause });
28
+ }
29
+ if (stage.version !== 2 || stage.input !== ref.integrity || stableJson(stage.ref) !== stableJson(ref))
30
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Capture reference does not match its retained manifest.');
31
+ return stage;
32
+ }
33
+ const recoveryCache = () => ({ snapshots: new Map(), bindings: new Map(), exports: new Map(), visiting: new Set() });
34
+ async function retained(cache, map, key, action) {
35
+ if (map.has(key))
36
+ return map.get(key);
37
+ if (cache.visiting.has(key))
38
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Retained native evidence contains a reference cycle.');
39
+ cache.visiting.add(key);
40
+ try {
41
+ const value = await action();
42
+ map.set(key, value);
43
+ return value;
44
+ }
45
+ finally {
46
+ cache.visiting.delete(key);
47
+ }
48
+ }
49
+ export async function regenerateExport(store, frames, bindingId, cache = recoveryCache()) {
50
+ return retained(cache, cache.exports, `export:${bindingId}`, async () => {
51
+ const prepared = frames.find(frame => frame.body.kind === 'binding' && frame.body.binding.id === bindingId);
52
+ const recipe = prepared?.body.recipe;
53
+ if (!recipe || recipe.codec !== MANAGED_CODEC_VERSION || recipe.entropy !== NATIVE_ENTROPY_VERSION)
54
+ throw new TrajectoriesError('UNSUPPORTED_CONTEXT', 'The exact export codec is unavailable; migrate before continuing.');
55
+ const sourceFrames = atHead(frames, recipe.atHead);
56
+ if (prepared.body.parent !== recipe.atHead)
57
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Export baseline must reference its exact parent.');
58
+ const capture = [...sourceFrames].reverse().find(frame => frame.body.capture)?.body.capture;
59
+ if (!capture)
60
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Export source capture is missing.');
61
+ const source = await recoverSnapshot(store, await loadStage(store, capture), sourceFrames, cache);
62
+ const native = convertManaged(source, prepared.body.binding.harness, recipe.write, recipe.seed, await exportRecords(store, sourceFrames, source));
63
+ if (native.files.length !== recipe.native.files.length || native.files.some((file, index) => {
64
+ const expected = recipe.native.files[index];
65
+ return !expected || file.path !== expected.path || sha256(file.content) !== expected.sha256 || Buffer.byteLength(file.content) !== expected.sizeBytes;
66
+ }))
67
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Frozen export does not reproduce all prepared native files.');
68
+ return native;
69
+ });
70
+ }
71
+ export async function bindingSnapshot(store, frames, bindingId, cache = recoveryCache()) {
72
+ return retained(cache, cache.bindings, `binding:${bindingId}`, async () => {
73
+ const frame = frames.find(frame => frame.body.kind === 'binding' && frame.body.binding.id === bindingId);
74
+ if (frame?.body.legacyBaseline)
75
+ return recoverEvidence(store, frame.body.legacyBaseline, frames, undefined, cache);
76
+ return readNativeExportSnapshot(await regenerateExport(store, frames, bindingId, cache));
77
+ });
78
+ }
79
+ export async function exportRecords(store, frames, source) {
80
+ const selected = selection(frames), byId = new Map(selected.history.map(record => [record.id, record]));
81
+ const context = await Promise.all(selected.context.map(id => loadRecord(store, byId.get(id))));
82
+ const meta = source.canonical.records.find(record => record.record_type === 'meta');
83
+ return [...(meta ? [JSON.parse(meta.record_json)] : []), ...context.map(record => record.payload)];
84
+ }
85
+ export async function recoverSnapshot(store, stage, frames, cache = recoveryCache()) {
86
+ return retained(cache, cache.snapshots, `capture:${stage.ref.id}`, () => recoverEvidence(store, stage.evidence, frames, stage.ref.id, cache));
87
+ }
88
+ export async function recoverEvidence(store, evidence, frames, ownId, cache = recoveryCache()) {
89
+ let derived;
90
+ if (evidence.derivedBinding) {
91
+ const native = await regenerateExport(store, frames, evidence.derivedBinding, cache);
92
+ const baseline = readNativeExportSnapshot(native);
93
+ derived = derivedChunks([baseline.raw, ...(baseline.attachments ?? []).map(a => a.content)]);
94
+ }
95
+ let raw = await getText(store, evidence.raw, derived);
96
+ const prefix = evidence.prefix;
97
+ if (prefix) {
98
+ let baseline;
99
+ if (prefix.capture) {
100
+ const frame = frames.find(frame => frame.body.capture?.id === prefix.capture), ref = frame?.body.capture;
101
+ if (!ref || ref.id === ownId)
102
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Missing or cyclic evidence prefix.');
103
+ baseline = await recoverSnapshot(store, await loadStage(store, ref), atHead(frames, frame.head), cache);
104
+ }
105
+ else if (prefix.binding)
106
+ baseline = await bindingSnapshot(store, frames, prefix.binding, cache);
107
+ else
108
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Evidence prefix has no retained source.');
109
+ if (sha256(baseline.raw) !== prefix.sha256 || Buffer.byteLength(baseline.raw) !== prefix.bytes)
110
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Evidence prefix integrity mismatch.');
111
+ raw = baseline.raw + raw;
112
+ }
113
+ const attachments = [];
114
+ for (const attachment of evidence.attachments)
115
+ attachments.push({ name: attachment.name,
116
+ content: await getText(store, attachment.text, derived), sha256: attachment.sha256 });
117
+ const header = evidence.header;
118
+ const snapshot = snapshotFromEvidence({ harness: header.source.harness, raw,
119
+ ...(header.source.nativeSessionId ? { nativeSessionId: header.source.nativeSessionId } : {}), attachments });
120
+ if (snapshot.id !== header.id || snapshot.source.sha256 !== header.source.sha256)
121
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Retained native evidence does not reproduce its captured canonical identity.');
122
+ return { ...snapshot, createdAt: header.createdAt, source: header.source };
123
+ }
@@ -0,0 +1,14 @@
1
+ import type { Snapshot } from './types.js';
2
+ import type { CaptureRef, NativeCheckpointLimits, StageOptions } from './log-types.js';
3
+ import { type StageData } from './log-state.js';
4
+ export interface IntentData {
5
+ input: string;
6
+ stage?: string;
7
+ export?: import('./log-state.js').ExportRecipe;
8
+ }
9
+ export declare function readIntent(store: string, id: string, intent: string): Promise<IntentData | null>;
10
+ export declare function saveIntent(store: string, id: string, intent: string, data: IntentData): Promise<void>;
11
+ export declare function checkpointLimits(value?: NativeCheckpointLimits): NativeCheckpointLimits;
12
+ export declare function normalizeStageOptions(options: StageOptions): Promise<StageOptions>;
13
+ export declare function stageCapture(store: string, snapshot: Snapshot, requested: StageOptions): Promise<CaptureRef>;
14
+ export declare function validateStageBacking(store: string, stage: StageData, frames: import('./log-state.js').Frame[]): Promise<void>;
@@ -0,0 +1,138 @@
1
+ import { readFile, realpath } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { TrajectoriesError } from './log-types.js';
4
+ import { assertPortableSnapshot, assertSnapshot, snapshotFromEvidence } from './convert.js';
5
+ import { atomicWrite, digest, getObject, putObject, safeId, stableJson, withSessionLock } from './log-io.js';
6
+ import { derivedChunks, putText } from './log-content.js';
7
+ import { intentPath, initializeLog, readFrames } from './log-state.js';
8
+ import { capturePath, loadStage, recoverSnapshot, regenerateExport } from './log-recovery.js';
9
+ import { planRecords } from './log-records.js';
10
+ import { units, loadRecord } from './log-records.js';
11
+ import { captureBaseline } from './log-baseline.js';
12
+ import { selection } from './log-read.js';
13
+ import { assertSnapshotContinuation } from './store.js';
14
+ import { readNativeExportSnapshot, sha256 } from './convert.js';
15
+ export async function readIntent(store, id, intent) {
16
+ try {
17
+ return JSON.parse(await readFile(intentPath(store, id, intent), 'utf8'));
18
+ }
19
+ catch (cause) {
20
+ if (cause.code === 'ENOENT')
21
+ return null;
22
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Intent binding is corrupt.', { cause });
23
+ }
24
+ }
25
+ export async function saveIntent(store, id, intent, data) {
26
+ await atomicWrite(intentPath(store, id, intent), stableJson(data));
27
+ }
28
+ export function checkpointLimits(value = { maxCount: 0, maxBytes: 0 }) {
29
+ if (!Number.isSafeInteger(value.maxCount) || !Number.isSafeInteger(value.maxBytes) || value.maxCount < 0 || value.maxBytes < 0)
30
+ throw new TrajectoriesError('INVALID_INPUT', 'Native checkpoint count and byte limits must be nonnegative safe integers.');
31
+ return { maxCount: value.maxCount, maxBytes: value.maxBytes };
32
+ }
33
+ export async function normalizeStageOptions(options) {
34
+ if (!options || typeof options !== 'object')
35
+ throw new TrajectoriesError('INVALID_INPUT', 'Stage options are required.');
36
+ safeId(options.sessionId);
37
+ safeId(options.intentId);
38
+ if (options.requireProgress !== undefined && typeof options.requireProgress !== 'boolean')
39
+ throw new TrajectoriesError('INVALID_INPUT', 'Invalid progress guard.');
40
+ if (options.expectedHead === null) {
41
+ let workspace;
42
+ try {
43
+ workspace = await realpath(resolve(options.workspace));
44
+ }
45
+ catch (cause) {
46
+ throw new TrajectoriesError('INVALID_INPUT', 'Workspace must exist.', { cause });
47
+ }
48
+ return { ...options, workspace, nativeCheckpoints: checkpointLimits(options.nativeCheckpoints), requireProgress: options.requireProgress ?? false };
49
+ }
50
+ if (!/^[a-f0-9]{64}$/.test(options.expectedHead))
51
+ throw new TrajectoriesError('INVALID_INPUT', 'Invalid expected head.');
52
+ return { ...options, requireProgress: options.requireProgress ?? false };
53
+ }
54
+ export async function stageCapture(store, snapshot, requested) {
55
+ const options = await normalizeStageOptions(requested);
56
+ try {
57
+ assertSnapshot(snapshot);
58
+ }
59
+ catch (cause) {
60
+ throw new TrajectoriesError('INVALID_INPUT', 'Capture must be a valid, intact Snapshot.', { cause });
61
+ }
62
+ try {
63
+ assertPortableSnapshot(snapshot);
64
+ const recovered = snapshotFromEvidence({ harness: snapshot.source.harness, raw: snapshot.raw,
65
+ ...(snapshot.source.nativeSessionId ? { nativeSessionId: snapshot.source.nativeSessionId } : {}), attachments: snapshot.attachments });
66
+ if (recovered.id !== snapshot.id)
67
+ throw new Error('Canonical input differs from its retained native evidence.');
68
+ }
69
+ catch (cause) {
70
+ throw new TrajectoriesError('UNSUPPORTED_CONTEXT', cause instanceof Error ? cause.message : 'Invalid captured history.', { cause });
71
+ }
72
+ const input = digest(stableJson({ operation: options.expectedHead === null ? 'create' : 'append', options, snapshot: snapshot.id }));
73
+ return withSessionLock(store, options.sessionId, async () => {
74
+ await initializeLog(store);
75
+ const frames = await readFrames(store, options.sessionId), current = frames.at(-1);
76
+ const existing = await readIntent(store, options.sessionId, options.intentId);
77
+ if (existing) {
78
+ if (existing.input !== input)
79
+ throw new TrajectoriesError('INTENT_CONFLICT', 'This intent is bound to different capture input or options.');
80
+ if (!existing.stage)
81
+ throw new TrajectoriesError('OPERATION_PENDING', 'This capture is still publishing; reconcile the original intent.');
82
+ const stage = JSON.parse((await getObject(store, existing.stage)).toString());
83
+ await loadStage(store, stage.ref);
84
+ await validateStageBacking(store, stage, frames);
85
+ return stage.ref;
86
+ }
87
+ if ((current?.head ?? null) !== options.expectedHead)
88
+ throw new TrajectoriesError('STALE_HEAD', 'The session head changed before staging.');
89
+ let baseline;
90
+ if (options.expectedHead !== null) {
91
+ baseline = await captureBaseline(store, frames, snapshot, options.bindingId);
92
+ try {
93
+ assertSnapshotContinuation(baseline.snapshot, snapshot, { requireProgress: options.requireProgress, allowInitialSystem: baseline.firstTarget });
94
+ }
95
+ catch (cause) {
96
+ throw new TrajectoriesError('UNSUPPORTED_CONTEXT', cause instanceof Error ? cause.message : 'Capture failed continuity validation.', { cause });
97
+ }
98
+ }
99
+ await saveIntent(store, options.sessionId, options.intentId, { input });
100
+ const binding = baseline?.binding ?? { id: `b_${digest(stableJson([options.sessionId, options.intentId]))}`,
101
+ harness: snapshot.source.harness, nativeSessionId: snapshot.source.nativeSessionId ?? '' };
102
+ const ref = { id: `c_${input}`, sessionId: options.sessionId, parent: options.expectedHead, intentId: options.intentId,
103
+ bindingId: binding.id, integrity: input, source: { harness: snapshot.source.harness, sha256: snapshot.source.sha256,
104
+ ...(snapshot.source.nativeSessionId ? { nativeSessionId: snapshot.source.nativeSessionId } : {}) } };
105
+ const planned = await planRecords({ store, sessionId: options.sessionId, intentId: options.intentId, observed: snapshot,
106
+ before: baseline?.before ?? [], beforeIds: baseline?.ids ?? [], beforeActive: baseline ? units(baseline.snapshot) : [],
107
+ beforeActiveIds: baseline?.activeIds ?? [], context: selection(frames).context, allowInitialSystem: baseline?.firstTarget,
108
+ origin: { harness: binding.harness, ...(binding.nativeSessionId ? { nativeSessionId: binding.nativeSessionId } : {}), bindingId: binding.id } });
109
+ const { raw, canonical, attachments, ...header } = snapshot;
110
+ const generated = baseline?.derivedBinding ? readNativeExportSnapshot(await regenerateExport(store, frames, baseline.derivedBinding)) : undefined;
111
+ const derived = generated ? derivedChunks([generated.raw, ...(generated.attachments ?? []).map(a => a.content)]) : undefined;
112
+ const prefix = baseline && raw.startsWith(baseline.snapshot.raw) ? { ...baseline.mapping,
113
+ sha256: sha256(baseline.snapshot.raw), bytes: Buffer.byteLength(baseline.snapshot.raw) } : undefined;
114
+ const evidence = { header, raw: await putText(store, prefix ? raw.slice(baseline.snapshot.raw.length) : raw, derived), attachments: [],
115
+ ...(baseline?.derivedBinding ? { derivedBinding: baseline.derivedBinding } : {}), ...(prefix ? { prefix } : {}) };
116
+ for (const attachment of attachments ?? [])
117
+ evidence.attachments.push({ name: attachment.name, sha256: attachment.sha256, text: await putText(store, attachment.content, derived) });
118
+ const meta = canonical.records.find(record => record.record_type === 'meta');
119
+ const stage = { version: 2, ref, input, options, evidence, records: planned.records, edit: planned.edit,
120
+ progress: planned.progress, diagnostics: await putText(store, stableJson(canonical.diagnostics)),
121
+ ...(meta ? { nativeMeta: JSON.parse(meta.record_json) } : {}), mapping: { ...baseline?.mapping, added: planned.added,
122
+ ...(planned.leading ? { leading: planned.leading } : {}) }, binding };
123
+ const object = await putObject(store, stableJson(stage));
124
+ await atomicWrite(capturePath(store, ref.id), stableJson({ object }), true);
125
+ await validateStageBacking(store, stage, frames);
126
+ await saveIntent(store, options.sessionId, options.intentId, { input, stage: object });
127
+ return ref;
128
+ });
129
+ }
130
+ export async function validateStageBacking(store, stage, frames) {
131
+ await recoverSnapshot(store, stage, frames);
132
+ const parent = stage.ref.parent;
133
+ const index = parent === null ? -1 : frames.findIndex(frame => frame.head === parent);
134
+ if (parent !== null && index < 0)
135
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Staged parent is missing.');
136
+ for (const record of [...selection(frames.slice(0, index + 1)).history, ...stage.records])
137
+ await loadRecord(store, record);
138
+ }
@@ -0,0 +1,89 @@
1
+ import type { Snapshot, WriteOptions } from './types.js';
2
+ import type { BindingId, CaptureId, CaptureRef, Head, NativeBinding, NativeCheckpointLimits, PreparedExport, RecordId, StageOptions } from './log-types.js';
3
+ import type { TextRef } from './log-content.js';
4
+ export interface StoredRecord {
5
+ id: RecordId;
6
+ object: string;
7
+ }
8
+ export interface ContextEdit {
9
+ start: number;
10
+ remove: number;
11
+ insert: RecordId[];
12
+ }
13
+ export interface ExportRecipe {
14
+ version: 1;
15
+ codec: string;
16
+ entropy: string;
17
+ seed: string;
18
+ atHead: Head;
19
+ write: WriteOptions;
20
+ native: PreparedExport;
21
+ }
22
+ export interface FrameBody {
23
+ parent: Head | null;
24
+ intentId: string;
25
+ input: string;
26
+ time: string;
27
+ kind: 'capture' | 'binding';
28
+ session: {
29
+ id: string;
30
+ workspace: string;
31
+ createdAt: string;
32
+ nativeCheckpoints: NativeCheckpointLimits;
33
+ };
34
+ records: StoredRecord[];
35
+ edit?: ContextEdit;
36
+ binding: NativeBinding;
37
+ progress: boolean;
38
+ diagnostics: TextRef;
39
+ nativeMeta?: import('@letta-ai/trajectory').NormalizedRecord;
40
+ capture?: CaptureRef;
41
+ stage?: string;
42
+ recipe?: ExportRecipe;
43
+ /** Migration-only seed for old exports whose random metadata cannot be regenerated. */
44
+ legacyBaseline?: StageData['evidence'];
45
+ }
46
+ export interface Frame {
47
+ head: Head;
48
+ body: FrameBody;
49
+ }
50
+ export interface StageData {
51
+ version: 2;
52
+ ref: CaptureRef;
53
+ input: string;
54
+ options: StageOptions;
55
+ evidence: {
56
+ header: Omit<Snapshot, 'raw' | 'canonical' | 'attachments'>;
57
+ raw: TextRef;
58
+ attachments: Array<{
59
+ name: string;
60
+ sha256: string;
61
+ text: TextRef;
62
+ }>;
63
+ derivedBinding?: BindingId;
64
+ prefix?: {
65
+ capture?: CaptureId;
66
+ binding?: BindingId;
67
+ sha256: string;
68
+ bytes: number;
69
+ };
70
+ };
71
+ records: StoredRecord[];
72
+ edit: ContextEdit;
73
+ progress: boolean;
74
+ diagnostics: TextRef;
75
+ nativeMeta?: import('@letta-ai/trajectory').NormalizedRecord;
76
+ mapping: {
77
+ capture?: CaptureId;
78
+ binding?: BindingId;
79
+ leading?: RecordId;
80
+ added: RecordId[];
81
+ };
82
+ binding: NativeBinding;
83
+ }
84
+ export declare const logPath: (store: string, id: string) => string;
85
+ export declare const intentPath: (store: string, id: string, intent: string) => string;
86
+ export declare function assertLogStore(store: string): Promise<void>;
87
+ export declare function initializeLog(store: string): Promise<void>;
88
+ export declare function readFrames(store: string, id: string): Promise<Frame[]>;
89
+ export declare function publishFrame(store: string, body: FrameBody): Promise<Frame>;
@@ -0,0 +1,92 @@
1
+ import { open, readFile, readdir } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { TrajectoriesError } from './log-types.js';
4
+ import { atomicWrite, digest, ensureDirectory, safeId, stableJson, syncDirectory } from './log-io.js';
5
+ export const logPath = (store, id) => join(resolve(store), 'logs', `${safeId(id)}.jsonl`);
6
+ export const intentPath = (store, id, intent) => join(resolve(store), 'intents', safeId(id), `${safeId(intent)}.json`);
7
+ export async function assertLogStore(store) {
8
+ try {
9
+ const legacy = await readdir(join(resolve(store), 'sessions'));
10
+ if (legacy.some(name => name.endsWith('.json')))
11
+ throw new TrajectoriesError('INVALID_INPUT', 'Version-1 session authority found; migrate this store before using the canonical log.');
12
+ }
13
+ catch (error) {
14
+ if (error.code !== 'ENOENT')
15
+ throw error;
16
+ }
17
+ const marker = join(resolve(store), 'format.json');
18
+ try {
19
+ const current = JSON.parse(await readFile(marker, 'utf8'));
20
+ if (current.version !== 2)
21
+ throw new TrajectoriesError('INVALID_INPUT', 'Store migration is required.');
22
+ }
23
+ catch (error) {
24
+ if (error.code === 'ENOENT')
25
+ return;
26
+ if (error instanceof TrajectoriesError)
27
+ throw error;
28
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'Store format marker is corrupt or unreadable.', { cause: error });
29
+ }
30
+ }
31
+ export async function initializeLog(store) {
32
+ await assertLogStore(store);
33
+ const marker = join(resolve(store), 'format.json');
34
+ try {
35
+ await readFile(marker);
36
+ }
37
+ catch (error) {
38
+ if (error.code !== 'ENOENT')
39
+ throw error;
40
+ try {
41
+ await atomicWrite(marker, stableJson({ version: 2 }), true);
42
+ }
43
+ catch (cause) {
44
+ if (cause.code !== 'EEXIST')
45
+ throw cause;
46
+ }
47
+ }
48
+ }
49
+ export async function readFrames(store, id) {
50
+ await assertLogStore(store);
51
+ let text;
52
+ try {
53
+ text = await readFile(logPath(store, id), 'utf8');
54
+ }
55
+ catch (error) {
56
+ if (error.code === 'ENOENT')
57
+ return [];
58
+ throw error;
59
+ }
60
+ if (!text.endsWith('\n'))
61
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'The canonical log has a partial tail; reconcile it before continuing.');
62
+ const frames = [];
63
+ for (const line of text.slice(0, -1).split('\n')) {
64
+ let frame;
65
+ try {
66
+ frame = JSON.parse(line);
67
+ }
68
+ catch (cause) {
69
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'The canonical log contains invalid JSON.', { cause });
70
+ }
71
+ if (!frame?.body || digest(stableJson(frame.body)) !== frame.head || frame.body.parent !== (frames.at(-1)?.head ?? null)
72
+ || frame.body.session.id !== id)
73
+ throw new TrajectoriesError('INTEGRITY_ERROR', 'The canonical log chain is corrupt.');
74
+ frames.push(frame);
75
+ }
76
+ return frames;
77
+ }
78
+ export async function publishFrame(store, body) {
79
+ const frame = { head: digest(stableJson(body)), body };
80
+ const path = logPath(store, body.session.id);
81
+ await ensureDirectory(dirname(path));
82
+ const file = await open(path, 'a', 0o600);
83
+ try {
84
+ await file.writeFile(stableJson(frame) + '\n');
85
+ await file.sync();
86
+ }
87
+ finally {
88
+ await file.close();
89
+ }
90
+ await syncDirectory(dirname(path));
91
+ return frame;
92
+ }
@@ -0,0 +1,3 @@
1
+ import { type Harness } from './types.js';
2
+ import type { PreparedSession, SwitchOptions } from './log-types.js';
3
+ export declare function switchSession(store: string, id: string, target: Harness, requested: SwitchOptions): Promise<PreparedSession>;