trajectories 0.0.0 → 0.1.2

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 (205) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -0
  3. package/README.md +288 -2
  4. package/dist/cli.js +30210 -0
  5. package/dist/convert.d.ts +31 -0
  6. package/dist/convert.js +431 -0
  7. package/dist/deepagents_checkpoint.py +362 -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 +1233 -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 +150 -0
  195. package/ops/install-cli.sh +63 -0
  196. package/ops/install-native.sh +35 -0
  197. package/ops/install-release.sh +49 -0
  198. package/ops/linux/install-systemd-user.sh +103 -0
  199. package/ops/macos/install-launchd.sh +72 -0
  200. package/ops/sync-settings.sh +37 -0
  201. package/package.json +21 -5
  202. package/skills/trajectories/SKILL.md +138 -0
  203. package/skills/trajectories/scripts/trajectories_recent.sh +26 -0
  204. package/skills/trajectories/scripts/trajectories_search.sh +26 -0
  205. package/index.js +0 -1
@@ -0,0 +1,31 @@
1
+ import { type NormalizedRecord } from '@letta-ai/trajectory';
2
+ import { type Harness, type Snapshot, type NativeExport, type WriteOptions } from './types.js';
3
+ export declare const sha256: (value: string) => string;
4
+ export interface SnapshotEvidence {
5
+ harness: Harness;
6
+ raw: string;
7
+ nativeSessionId?: string;
8
+ attachments?: readonly NonNullable<Snapshot['attachments']>[number][];
9
+ }
10
+ /** Reconstruct captured native evidence without reading a path or writing an archive. */
11
+ export declare function snapshotFromEvidence(input: SnapshotEvidence): Snapshot;
12
+ /** Read an in-memory generated export through the same evidence contract as capture. */
13
+ export declare function readNativeExportSnapshot(native: NativeExport): Snapshot;
14
+ export declare function readSnapshot(input: {
15
+ harness: Harness;
16
+ path?: string;
17
+ transcript?: string;
18
+ /** Select one native session in a database, or verify a file's native identity. */
19
+ nativeSessionId?: string;
20
+ /** Python containing the native Deep Agents checkpoint dependencies. */
21
+ pythonExecutable?: string;
22
+ }): Promise<Snapshot>;
23
+ export declare function assertSnapshot(snapshot: Snapshot): void;
24
+ /** Complete portable context, stricter than an archive's integrity-only validation. */
25
+ export declare function assertPortableSnapshot(snapshot: Snapshot): void;
26
+ export declare function convert(snapshot: Snapshot, target: Harness, options: WriteOptions): NativeExport;
27
+ /** Internal managed-store codec; its caller freezes the destination identity and time. */
28
+ export declare function convertManaged(snapshot: Snapshot, target: Harness, options: WriteOptions, entropySeed: string, records?: readonly NormalizedRecord[]): NativeExport;
29
+ /** Write only to a new directory; an existing destination is never modified. */
30
+ export declare function writeNativeExport(output: NativeExport, directory: string): Promise<void>;
31
+ export declare function writeSnapshot(snapshot: Snapshot, target: Harness, options: WriteOptions): Promise<NativeExport>;
@@ -0,0 +1,431 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile, mkdir, writeFile, realpath } from 'node:fs/promises';
3
+ import { resolve, dirname, relative, isAbsolute, sep, join, basename } from 'node:path';
4
+ import { normalizeToCanonical, validateTranscript } from '@letta-ai/trajectory';
5
+ import { HARNESSES } from './types.js';
6
+ import { createSeededNativeEntropy } from './writers/entropy.js';
7
+ import { writePi } from './writers/pi.js';
8
+ import { writeOmp } from './writers/omp.js';
9
+ import { writeCodex } from './writers/codex.js';
10
+ import { writeClaudeCode } from './writers/claude-code.js';
11
+ import { writeOpenCode } from './writers/opencode.js';
12
+ import { writeGrokBuild } from './writers/grok-build.js';
13
+ import { writeCursor } from './writers/cursor.js';
14
+ import { writeDeepAgents } from './writers/deepagents.js';
15
+ import { writeGitHubCopilot } from './writers/github-copilot.js';
16
+ import { writeCline } from './writers/cline.js';
17
+ import { writeFx } from './writers/fx.js';
18
+ import { normalizeGrokBuild } from './readers/grok-build.js';
19
+ import { readCursor, readCursorNative } from './readers/cursor.js';
20
+ import { readDeepAgents, readDeepAgentsSeed } from './readers/deepagents.js';
21
+ import { readCline } from './readers/cline.js';
22
+ import { readGitHubCopilot } from './readers/github-copilot.js';
23
+ import { readFx } from './readers/fx.js';
24
+ import { assertPortableHistory } from './readers/guards.js';
25
+ import { projectPiProviderContext, normalizePiArchive } from './readers/pi-context.js';
26
+ import { piCompactedContext } from './readers/pi-compaction.js';
27
+ import { toolNames } from './writers/helpers.js';
28
+ export const sha256 = (value) => createHash('sha256').update(value).digest('hex');
29
+ function exactUtf8(bytes, label) {
30
+ const content = bytes.toString('utf8');
31
+ if (!Buffer.from(content, 'utf8').equals(bytes))
32
+ throw new Error(`${label} is not valid UTF-8.`);
33
+ return content;
34
+ }
35
+ function fxArtifactRefs(events) {
36
+ const refs = new Set();
37
+ for (const [index, line] of events.split('\n').entries()) {
38
+ if (!line)
39
+ continue;
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(line);
43
+ }
44
+ catch {
45
+ throw new Error(`FX events.jsonl line ${index + 1} is invalid JSON.`);
46
+ }
47
+ const ref = parsed?.event?.tool_result?.artifact_ref;
48
+ if (ref === undefined)
49
+ continue;
50
+ if (typeof ref !== 'string' || !ref || basename(ref) !== ref || ref === '.' || ref === '..' || /[\x00-\x1f]/.test(ref)) {
51
+ throw new Error(`FX events.jsonl line ${index + 1} has an unsafe tool-result artifact_ref.`);
52
+ }
53
+ refs.add(ref);
54
+ }
55
+ return [...refs];
56
+ }
57
+ function fxCompanionsFromAttachments(attachments) {
58
+ let events;
59
+ const toolResults = Object.create(null);
60
+ for (const attachment of attachments) {
61
+ if (attachment.name === 'events.jsonl') {
62
+ if (events !== undefined)
63
+ throw new Error('FX snapshot has duplicate events.jsonl attachments.');
64
+ events = attachment.content;
65
+ }
66
+ else if (attachment.name.startsWith('tool-results/')) {
67
+ const ref = attachment.name.slice('tool-results/'.length);
68
+ if (!ref || basename(ref) !== ref || Object.hasOwn(toolResults, ref))
69
+ throw new Error('FX snapshot has an unsafe or duplicate tool-result attachment.');
70
+ toolResults[ref] = attachment.content;
71
+ }
72
+ }
73
+ return { ...(events === undefined ? {} : { events }), ...(Object.keys(toolResults).length ? { toolResults } : {}) };
74
+ }
75
+ async function captureFxCompanions(sessionPath, raw) {
76
+ let root;
77
+ try {
78
+ root = JSON.parse(raw);
79
+ }
80
+ catch {
81
+ return { attachments: [], companions: {} };
82
+ }
83
+ if (root?.schema_version !== 4)
84
+ return { attachments: [], companions: {} };
85
+ const sessionDirectory = await realpath(dirname(sessionPath));
86
+ const eventsPath = await realpath(join(sessionDirectory, 'events.jsonl'));
87
+ if (relative(sessionDirectory, eventsPath) !== 'events.jsonl')
88
+ throw new Error('FX events.jsonl resolves outside its native session directory.');
89
+ const events = exactUtf8(await readFile(eventsPath), 'FX events.jsonl');
90
+ const attachments = [{ name: 'events.jsonl', content: events, sha256: sha256(events) }];
91
+ const refs = fxArtifactRefs(events);
92
+ if (refs.length) {
93
+ const resultDirectory = await realpath(join(sessionDirectory, 'tool-results'));
94
+ const relativeResultDirectory = relative(sessionDirectory, resultDirectory);
95
+ if (!relativeResultDirectory || relativeResultDirectory === '..' || relativeResultDirectory.startsWith(`..${sep}`) || isAbsolute(relativeResultDirectory)) {
96
+ throw new Error('FX tool-results resolves outside its native session directory.');
97
+ }
98
+ for (const ref of refs) {
99
+ const artifactPath = await realpath(join(resultDirectory, ref));
100
+ if (relative(resultDirectory, artifactPath) !== ref)
101
+ throw new Error(`FX tool-results/${ref} resolves outside its native result directory.`);
102
+ const content = exactUtf8(await readFile(artifactPath), `FX tool-results/${ref}`);
103
+ attachments.push({ name: `tool-results/${ref}`, content, sha256: sha256(content) });
104
+ }
105
+ }
106
+ return { attachments, companions: fxCompanionsFromAttachments(attachments) };
107
+ }
108
+ function provenance(harness, canonical) {
109
+ const meta = canonical.records.find(record => record.record_type === 'meta');
110
+ let nativeSessionId = meta?.source_group_id && meta.source_group_id !== 'default' ? meta.source_group_id : undefined;
111
+ if (harness === 'deepagents' && nativeSessionId) {
112
+ const group = JSON.parse(nativeSessionId);
113
+ if (!Array.isArray(group) || group.length !== 2 || typeof group[0] !== 'string' || group[1] !== '')
114
+ throw new Error('Deep Agents requires an identified root checkpoint namespace.');
115
+ nativeSessionId = group[0];
116
+ }
117
+ const cwd = meta ? JSON.parse(meta.record_json).cwd : undefined;
118
+ return { nativeSessionId, cwd };
119
+ }
120
+ /** Reconstruct captured native evidence without reading a path or writing an archive. */
121
+ export function snapshotFromEvidence(input) {
122
+ if (!HARNESSES.includes(input.harness) || typeof input.raw !== 'string')
123
+ throw new Error('Invalid snapshot evidence harness or transcript.');
124
+ const attachments = input.attachments ?? [];
125
+ for (const attachment of attachments) {
126
+ if (typeof attachment.content !== 'string' || sha256(attachment.content) !== attachment.sha256)
127
+ throw new Error('Snapshot attachment integrity check failed.');
128
+ }
129
+ const manifest = input.harness === 'cline'
130
+ ? attachments.find(attachment => attachment.name.endsWith('.json') && !attachment.name.endsWith('.compaction.json')) : undefined;
131
+ const cline = manifest ? JSON.parse(manifest.content) : {};
132
+ if (input.nativeSessionId && cline.session_id && cline.session_id !== input.nativeSessionId)
133
+ throw new Error('Selected Cline session disagrees with its manifest.');
134
+ const summary = input.harness === 'grok-build' ? attachments.find(attachment => attachment.name === 'summary.json') : undefined;
135
+ const grok = summary ? JSON.parse(summary.content) : {};
136
+ let archived;
137
+ switch (input.harness) {
138
+ case 'grok-build':
139
+ archived = normalizeGrokBuild(input.raw, { sessionId: grok.info?.id, cwd: grok.info?.cwd, model: grok.current_model_id });
140
+ break;
141
+ case 'cursor':
142
+ archived = readCursor(input.raw);
143
+ break;
144
+ case 'deepagents':
145
+ archived = readDeepAgentsSeed(input.raw).canonical;
146
+ break;
147
+ case 'github-copilot':
148
+ archived = readGitHubCopilot(input.raw);
149
+ break;
150
+ case 'fx':
151
+ archived = readFx(input.raw, fxCompanionsFromAttachments(attachments));
152
+ break;
153
+ case 'cline':
154
+ archived = readCline(input.raw, { sessionId: cline.session_id, cwd: cline.cwd, model: cline.model,
155
+ ...(input.nativeSessionId ? { sessionId: input.nativeSessionId } : {}) });
156
+ break;
157
+ default: archived = normalizeToCanonical({ source: input.harness, transcript: input.raw,
158
+ ...(input.nativeSessionId ? { sourceContext: { groupId: input.nativeSessionId } } : {}),
159
+ bounds: { toolArguments: { maxCharacters: null }, toolResults: { maxCharacters: null } },
160
+ filters: { systemMessages: 'include', toolResults: 'include' },
161
+ });
162
+ }
163
+ return finalizeSnapshot(input, archived, Boolean(cline.compaction_path));
164
+ }
165
+ /** Read an in-memory generated export through the same evidence contract as capture. */
166
+ export function readNativeExportSnapshot(native) {
167
+ const files = new Map();
168
+ for (const file of native.files) {
169
+ if (files.has(file.path))
170
+ throw new Error('Native export has duplicate file paths.');
171
+ files.set(file.path, file.content);
172
+ }
173
+ const raw = files.get(native.entrypoint);
174
+ if (raw === undefined)
175
+ throw new Error('Native export entrypoint is missing.');
176
+ const attachments = [];
177
+ const attach = (path, name = basename(path), required = false) => {
178
+ const content = files.get(path);
179
+ if (content === undefined) {
180
+ if (required)
181
+ throw new Error(`Native export is missing required sidecar ${path}.`);
182
+ return;
183
+ }
184
+ attachments.push({ name, content, sha256: sha256(content) });
185
+ return content;
186
+ };
187
+ const directory = dirname(native.entrypoint);
188
+ if (native.harness === 'grok-build' && basename(native.entrypoint) === 'chat_history.jsonl')
189
+ attach(join(directory, 'summary.json'));
190
+ if (native.harness === 'cline' && native.entrypoint.endsWith('.messages.json')) {
191
+ const content = attach(native.entrypoint.slice(0, -'.messages.json'.length) + '.json');
192
+ const manifest = content ? JSON.parse(content) : {};
193
+ if (manifest.compaction_path && !isAbsolute(manifest.compaction_path)) {
194
+ const declared = join(directory, manifest.compaction_path);
195
+ if (declared !== '..' && !declared.startsWith(`..${sep}`))
196
+ attach(declared, 'declared.compaction.json');
197
+ }
198
+ attach(native.entrypoint.slice(0, -'.messages.json'.length) + '.compaction.json');
199
+ }
200
+ if (native.harness === 'fx' && JSON.parse(raw)?.schema_version === 4) {
201
+ const events = attach(join(directory, 'events.jsonl'), 'events.jsonl', true);
202
+ for (const ref of fxArtifactRefs(events))
203
+ attach(join(directory, 'tool-results', ref), `tool-results/${ref}`, true);
204
+ }
205
+ return snapshotFromEvidence({ harness: native.harness, raw, nativeSessionId: native.sessionId, attachments });
206
+ }
207
+ function finalizeSnapshot(input, archived, clineCompaction = false) {
208
+ const raw = input.raw, attachments = [...(input.attachments ?? [])];
209
+ const fxCompanions = input.harness === 'fx' ? fxCompanionsFromAttachments(attachments) : {};
210
+ // Corrupted source bytes are not a usable checkpoint, even when the normalizer
211
+ // can recover enough lines to render a transcript.
212
+ const invalid = archived.diagnostics.filter(d => d.code === 'invalid_json_line' || d.code === 'non_object_json_line');
213
+ if (invalid.length)
214
+ throw new Error(`Invalid native transcript: ${invalid.map(d => d.message).join('; ')}`);
215
+ const canonical = input.harness === 'pi' ? projectPiProviderContext(raw, archived) : archived;
216
+ const metadata = provenance(input.harness, canonical);
217
+ if (input.nativeSessionId && metadata.nativeSessionId !== input.nativeSessionId)
218
+ throw new Error('Selected native session does not match source provenance.');
219
+ const hash = sha256(raw);
220
+ const portabilityIssues = [];
221
+ try {
222
+ assertPortableHistory(input.harness, raw, fxCompanions);
223
+ }
224
+ catch (error) {
225
+ portabilityIssues.push({ code: 'unsupported_active_context', message: error instanceof Error ? error.message : String(error) });
226
+ }
227
+ if (clineCompaction || attachments.some(attachment => attachment.name.endsWith('.compaction.json')))
228
+ portabilityIssues.push({ code: 'unsupported_active_context', message: 'Cline has native compaction state requiring context reconstruction.' });
229
+ return {
230
+ version: 1,
231
+ id: sha256(`${input.harness}\0${hash}\0${JSON.stringify(canonical)}\0${JSON.stringify(attachments)}`),
232
+ createdAt: new Date().toISOString(),
233
+ source: {
234
+ harness: input.harness,
235
+ ...(metadata.nativeSessionId ? { nativeSessionId: metadata.nativeSessionId } : {}),
236
+ sha256: hash,
237
+ },
238
+ ...(metadata.cwd ? { cwd: metadata.cwd } : {}),
239
+ raw,
240
+ ...(attachments.length ? { attachments } : {}),
241
+ canonical,
242
+ ...(portabilityIssues.length ? { portabilityIssues } : {}),
243
+ };
244
+ }
245
+ export async function readSnapshot(input) {
246
+ if (!HARNESSES.includes(input.harness))
247
+ throw new Error(`Unsupported harness: ${input.harness}`);
248
+ if ((input.path === undefined) === (input.transcript === undefined)) {
249
+ throw new Error('Supply exactly one of path or transcript.');
250
+ }
251
+ let raw;
252
+ const attachments = [];
253
+ if (input.harness === 'cursor' && input.path && !input.path.endsWith('.json')) {
254
+ const captured = await readCursorNative(input.path, input.nativeSessionId);
255
+ raw = captured.raw;
256
+ attachments.push(...captured.attachments);
257
+ }
258
+ else if (input.harness === 'deepagents' && input.path && !input.path.endsWith('.json')) {
259
+ if (!input.nativeSessionId)
260
+ throw new Error('Deep Agents SQLite input requires nativeSessionId (the thread ID).');
261
+ const captured = await readDeepAgents({ path: input.path, threadId: input.nativeSessionId, pythonExecutable: input.pythonExecutable });
262
+ raw = captured.raw;
263
+ }
264
+ else {
265
+ raw = input.transcript ?? await readFile(input.path, 'utf8');
266
+ if (input.harness === 'fx' && input.path) {
267
+ const captured = await captureFxCompanions(input.path, raw);
268
+ attachments.push(...captured.attachments);
269
+ }
270
+ if (input.harness === 'grok-build' && input.path && basename(input.path) === 'chat_history.jsonl') {
271
+ try {
272
+ const content = await readFile(join(dirname(input.path), 'summary.json'), 'utf8');
273
+ attachments.push({ name: 'summary.json', content, sha256: sha256(content) });
274
+ }
275
+ catch (error) {
276
+ if (error.code !== 'ENOENT')
277
+ throw error;
278
+ }
279
+ }
280
+ switch (input.harness) {
281
+ case 'cline': {
282
+ if (input.path && input.path.endsWith('.messages.json')) {
283
+ const manifestPath = input.path.slice(0, -'.messages.json'.length) + '.json';
284
+ try {
285
+ const content = await readFile(manifestPath, 'utf8');
286
+ const manifest = JSON.parse(content);
287
+ attachments.push({ name: basename(manifestPath), content, sha256: sha256(content) });
288
+ if (manifest.compaction_path) {
289
+ // A declared compaction state blocks conversion even if unavailable.
290
+ // Archive contained state only; a manifest cannot redirect reads elsewhere.
291
+ const parent = resolve(dirname(input.path));
292
+ const declared = resolve(parent, manifest.compaction_path);
293
+ const rel = relative(parent, declared);
294
+ if (rel && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) {
295
+ try {
296
+ const real = await realpath(declared);
297
+ const realRel = relative(await realpath(parent), real);
298
+ if (realRel && realRel !== '..' && !realRel.startsWith(`..${sep}`) && !isAbsolute(realRel)) {
299
+ const content = await readFile(real, 'utf8');
300
+ attachments.push({ name: 'declared.compaction.json', content, sha256: sha256(content) });
301
+ }
302
+ }
303
+ catch (error) {
304
+ if (error.code !== 'ENOENT')
305
+ throw error;
306
+ }
307
+ }
308
+ }
309
+ }
310
+ catch (error) {
311
+ if (error.code !== 'ENOENT')
312
+ throw error;
313
+ }
314
+ const compactionPath = input.path.slice(0, -'.messages.json'.length) + '.compaction.json';
315
+ try {
316
+ const content = await readFile(compactionPath, 'utf8');
317
+ attachments.push({ name: basename(compactionPath), content, sha256: sha256(content) });
318
+ }
319
+ catch (error) {
320
+ if (error.code !== 'ENOENT')
321
+ throw error;
322
+ }
323
+ }
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ const snapshot = snapshotFromEvidence({ harness: input.harness, raw, nativeSessionId: input.nativeSessionId, attachments });
329
+ return input.path ? { ...snapshot, source: { ...snapshot.source, path: resolve(input.path) } } : snapshot;
330
+ }
331
+ const writers = {
332
+ 'claude-code': writeClaudeCode, codex: writeCodex, pi: writePi,
333
+ omp: writeOmp, opencode: writeOpenCode, 'grok-build': writeGrokBuild,
334
+ cursor: writeCursor, deepagents: writeDeepAgents, 'github-copilot': writeGitHubCopilot,
335
+ cline: writeCline, fx: writeFx,
336
+ };
337
+ export function assertSnapshot(snapshot) {
338
+ if (!snapshot || snapshot.version !== 1 || !HARNESSES.includes(snapshot.source?.harness))
339
+ throw new Error('Invalid snapshot version or harness.');
340
+ if (typeof snapshot.raw !== 'string' || sha256(snapshot.raw) !== snapshot.source.sha256)
341
+ throw new Error('Snapshot native transcript integrity check failed.');
342
+ const canonical = snapshot.canonical;
343
+ const id = sha256(`${snapshot.source.harness}\0${snapshot.source.sha256}\0${JSON.stringify(canonical)}\0${JSON.stringify(snapshot.attachments ?? [])}`);
344
+ if (snapshot.id !== id)
345
+ throw new Error('Snapshot identity check failed.');
346
+ if (!canonical.records.length)
347
+ throw new Error('Snapshot has no canonical records.');
348
+ const metadata = provenance(snapshot.source.harness, canonical);
349
+ if (snapshot.source.nativeSessionId !== metadata.nativeSessionId || snapshot.cwd !== metadata.cwd)
350
+ throw new Error('Snapshot source metadata disagrees with canonical provenance.');
351
+ for (const attachment of snapshot.attachments ?? []) {
352
+ if (sha256(attachment.content) !== attachment.sha256)
353
+ throw new Error('Snapshot attachment integrity check failed.');
354
+ }
355
+ for (const record of canonical.records) {
356
+ if (sha256(record.record_json) !== record.record_hash)
357
+ throw new Error('Snapshot canonical record integrity check failed.');
358
+ }
359
+ validateTranscript(canonical.records.map(r => JSON.parse(r.record_json)));
360
+ }
361
+ /** Complete portable context, stricter than an archive's integrity-only validation. */
362
+ export function assertPortableSnapshot(snapshot) {
363
+ assertSnapshot(snapshot);
364
+ const clineCompaction = snapshot.source.harness === 'cline' && snapshot.attachments?.some(attachment => attachment.name.endsWith('.compaction.json') || (attachment.name.endsWith('.json') && JSON.parse(attachment.content).compaction_path));
365
+ if (snapshot.portabilityIssues?.length || clineCompaction)
366
+ throw new Error(`Unsupported active context: ${snapshot.portabilityIssues?.map(issue => issue.message).join('; ') ?? 'native Cline compaction state'}`);
367
+ assertPortableHistory(snapshot.source.harness, snapshot.raw, snapshot.source.harness === 'fx' ? fxCompanionsFromAttachments(snapshot.attachments ?? []) : {});
368
+ if (snapshot.source.harness === 'pi' && piCompactedContext(snapshot.raw)
369
+ && JSON.stringify(projectPiProviderContext(snapshot.raw, normalizePiArchive(snapshot.raw))) !== JSON.stringify(snapshot.canonical)) {
370
+ throw new Error('Compacted Pi snapshot must be recaptured with its verified native active context before conversion.');
371
+ }
372
+ if (snapshot.source.harness === 'github-copilot' && JSON.stringify(readGitHubCopilot(snapshot.raw)) !== JSON.stringify(snapshot.canonical)) {
373
+ throw new Error('Copilot snapshot must be recaptured with complete native messages before conversion.');
374
+ }
375
+ toolNames(snapshot.canonical.records.map(record => JSON.parse(record.record_json)));
376
+ }
377
+ export function convert(snapshot, target, options) {
378
+ return convertWithEntropy(snapshot, target, options);
379
+ }
380
+ /** Internal managed-store codec; its caller freezes the destination identity and time. */
381
+ export function convertManaged(snapshot, target, options, entropySeed, records) {
382
+ if (!options.sessionId || !options.timestamp)
383
+ throw new Error('Managed native conversion requires a frozen sessionId and timestamp.');
384
+ return convertWithEntropy(snapshot, target, options, createSeededNativeEntropy(entropySeed), records);
385
+ }
386
+ function convertWithEntropy(snapshot, target, options, entropy, retainedRecords) {
387
+ assertPortableSnapshot(snapshot);
388
+ if (!HARNESSES.includes(target))
389
+ throw new Error(`Unsupported target: ${target}`);
390
+ if (!isAbsolute(options.cwd) || !isAbsolute(options.directory))
391
+ throw new Error('cwd and directory must be absolute paths.');
392
+ const records = retainedRecords ?? snapshot.canonical.records.map(r => JSON.parse(r.record_json));
393
+ if (retainedRecords) {
394
+ validateTranscript([...retainedRecords]);
395
+ toolNames(retainedRecords);
396
+ }
397
+ const output = writers[target](records, options, entropy);
398
+ output.diagnostics.unshift({ code: 'history_only', message: 'Converted conversation history. Runtime caches, credentials, permissions, tools, and workspace files are not transferred.' });
399
+ for (const diagnostic of snapshot.canonical.diagnostics) {
400
+ output.diagnostics.push({ code: `source_${diagnostic.code}`, message: diagnostic.message });
401
+ }
402
+ return output;
403
+ }
404
+ /** Write only to a new directory; an existing destination is never modified. */
405
+ export async function writeNativeExport(output, directory) {
406
+ const root = resolve(directory);
407
+ const names = new Set();
408
+ for (const file of output.files) {
409
+ const local = relative(root, resolve(root, file.path));
410
+ if (!local || local === '..' || local.startsWith(`..${sep}`) || isAbsolute(file.path) || names.has(local)) {
411
+ throw new Error(`Unsafe or duplicate native export path: ${file.path}`);
412
+ }
413
+ names.add(local);
414
+ }
415
+ if (!names.has(output.entrypoint))
416
+ throw new Error('Native export entrypoint is missing.');
417
+ await mkdir(dirname(root), { recursive: true });
418
+ await mkdir(root, { mode: 0o700 });
419
+ // Root is new and owned by this operation. All files are private and exclusive.
420
+ for (const file of output.files) {
421
+ const destination = resolve(root, file.path);
422
+ await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
423
+ await writeFile(destination, file.content, { flag: 'wx', mode: 0o600 });
424
+ }
425
+ }
426
+ export async function writeSnapshot(snapshot, target, options) {
427
+ await realpath(options.cwd); // Validate an existing workspace, not a guessed remap.
428
+ const output = convert(snapshot, target, options);
429
+ await writeNativeExport(output, options.directory);
430
+ return output;
431
+ }