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,178 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { mkdir, realpath, stat } from 'node:fs/promises';
5
+ import { parseArgs } from 'node:util';
6
+ import { getSession, listSessions, getHeadMetadata, readCanonicalSession } from '../../dist/log.js';
7
+ import type { ConversationRecord, HeadMetadata, ManagedSession, PageCursor } from '../../dist/log-types.js';
8
+ import type { CanonicalRecord } from '@letta-ai/trajectory';
9
+ import { resolveCanonicalDbPath } from './canonical.js';
10
+ import { TrajectoryDb } from './db.js';
11
+ import { TrajectoryReader } from './reader.js';
12
+ import { aggregateIngestDiagnostics } from './diagnostics.js';
13
+ import { ensurePrivateDirectory } from './private-storage.js';
14
+ import { acquireWriterLease, isMemoryStorePath } from './writer-lease.js';
15
+ import type { CanonicalDbRecord, SessionRecord } from './types.js';
16
+
17
+ const help = `trajectories index --store <directory> [--db <sqlite-file>] [--robot]
18
+ Index each managed session's current immutable head in the existing search DB.
19
+ Re-run after importing completed history; this does not watch or run an agent.
20
+ Search IDs are managed:<central-id>; accepted history remains in its session log.
21
+ trajectories show <central-id> --store <directory> [--robot]
22
+ trajectories show <indexed-id> [--db <sqlite-file>] [--robot]
23
+ Read a complete conversation. --store reads the accepted canonical log directly;
24
+ otherwise show uses the read-only search DB. --robot/--json retains all records.
25
+ `;
26
+
27
+ function storeRoot(value?: string): string {
28
+ return resolve(value ?? process.env.TRAJECTORIES_HOME ?? join(process.env.XDG_DATA_HOME ?? join(homedir(), '.local', 'share'), 'trajectories'));
29
+ }
30
+
31
+ function logPath(root: string, id: string): string { return join(root, 'logs', `${id}.jsonl`); }
32
+ type CanonicalConversationRecord = ConversationRecord & { canonical: CanonicalRecord };
33
+
34
+ async function history(root: string, session: ManagedSession): Promise<CanonicalConversationRecord[]> {
35
+ const records: CanonicalConversationRecord[] = [];
36
+ let cursor: PageCursor | undefined;
37
+ do {
38
+ const page = await readCanonicalSession(root, session.id, { atHead: session.head, view: 'history', cursor, limit: 1000 });
39
+ records.push(...page.items);
40
+ cursor = page.nextCursor ?? undefined;
41
+ } while (cursor);
42
+ return records;
43
+ }
44
+
45
+ async function sessionsAtRead(root: string): Promise<ManagedSession[]> {
46
+ const sessions: ManagedSession[] = [];
47
+ let cursor: PageCursor | undefined;
48
+ do {
49
+ const page = await listSessions(root, { cursor, limit: 1000 });
50
+ sessions.push(...page.items);
51
+ cursor = page.nextCursor ?? undefined;
52
+ } while (cursor);
53
+ return sessions;
54
+ }
55
+
56
+ /** Stable central identity, isolated from native-session and record identities. */
57
+ function project(session: ManagedSession, accepted: readonly CanonicalConversationRecord[], root: string, metadata: HeadMetadata): { session: SessionRecord; records: CanonicalDbRecord[] } {
58
+ const id = `managed:${session.id}`;
59
+ // The index origin is central. Native record.source_type remains unchanged;
60
+ // keeping this source separate also isolates native admission tombstones.
61
+ const source = 'managed';
62
+ const records: CanonicalDbRecord[] = accepted.map(({ id: centralId, canonical: record, origin }, index) => ({
63
+ id: `${id}:${centralId}`, session_id: id,
64
+ source_type: origin.harness, native_session_id: origin.nativeSessionId ?? null, binding_id: origin.bindingId ?? null,
65
+ record_type: record.record_type,
66
+ stable_source_record_id: record.stable_source_record_id ?? null,
67
+ source_identity_kind: record.source_identity_kind ?? null,
68
+ // Native order restarts after a handoff. The pinned accepted page order is
69
+ // authoritative for the complete logical conversation.
70
+ source_order_id: String(index).padStart(16, '0'), component_index: record.component_index,
71
+ record_timestamp: record.record_timestamp ?? null, source_timestamp: record.source_timestamp ?? null,
72
+ content: record.content ?? null, tool_call_id: record.tool_call_id ?? null, tool_name: record.tool_name ?? null,
73
+ tool_arguments_json: record.tool_arguments_json ?? null, tool_result_json: record.tool_result_json ?? null,
74
+ tool_result_ok: record.tool_result_ok === true ? 1 : record.tool_result_ok === false ? 0 : null,
75
+ record_json: record.record_json, content_hash: record.content_hash, record_hash: record.record_hash,
76
+ }));
77
+ const timestamps = records.map(record => record.source_timestamp).filter((value): value is string => !!value).sort();
78
+ const model = metadata.nativeMeta?.role === 'meta' ? metadata.nativeMeta.model : undefined;
79
+ return { records, session: {
80
+ id, source, source_path: logPath(root, session.id),
81
+ content_hash: createHash('sha256').update(`managed-log-head-v1:${session.head}`).digest('hex'),
82
+ title: records.find(record => record.record_type === 'user' && record.content?.trim())?.content?.slice(0, 120) ?? `Managed session ${session.id}`,
83
+ cwd: session.workspace, model: typeof model === 'string' ? model : null,
84
+ started_at: timestamps[0] ?? null, ended_at: timestamps.at(-1) ?? null,
85
+ record_count: records.length, created_at: session.createdAt, updated_at: session.updatedAt,
86
+ }};
87
+ }
88
+
89
+ function printRecords(id: string, source: string, path: string, records: ReadonlyArray<{
90
+ record_type: string; source_timestamp?: string | null; content?: string | null;
91
+ tool_call_id?: string | null; tool_name?: string | null; tool_arguments_json?: string | null;
92
+ tool_result_json?: string | null; record_json: string; source_type?: string;
93
+ native_session_id?: string | null;
94
+ }>): void {
95
+ console.log(`Session: ${id}\nSource: ${source}\nHistory: ${path}`);
96
+ for (const record of records) {
97
+ if (record.record_type === 'meta') continue;
98
+ const label = [record.source_timestamp, record.record_type, record.source_type, record.native_session_id, record.tool_name, record.tool_call_id].filter(Boolean).join(' ');
99
+ const body = record.record_type === 'assistant-tool-call' ? record.tool_arguments_json ?? record.record_json
100
+ : record.content ?? record.tool_result_json ?? record.record_json;
101
+ console.log(`\n[${label}]\n${body}`);
102
+ }
103
+ }
104
+
105
+ export async function runManagedCli(command: 'index' | 'show', argv: string[]): Promise<void> {
106
+ const { values, positionals } = parseArgs({ args: argv, allowPositionals: true, strict: true, options: {
107
+ store: { type: 'string' }, db: { type: 'string' }, robot: { type: 'boolean' }, json: { type: 'boolean' },
108
+ help: { type: 'boolean', short: 'h' },
109
+ }});
110
+ if (values.help) { console.log(help); return; }
111
+ const json = values.robot || values.json;
112
+ const emit = (value: unknown) => console.log(JSON.stringify(value, null, values.robot ? undefined : 2));
113
+ if (command === 'show') {
114
+ if (positionals.length !== 1) throw new Error('show requires exactly one session id.');
115
+ const id = positionals[0]!;
116
+ if (values.store) {
117
+ if (values.db) throw new Error('show accepts either --store or --db.');
118
+ const root = storeRoot(values.store);
119
+ const session = await getSession(root, id.startsWith('managed:') ? id.slice(8) : id);
120
+ const records = await history(root, session);
121
+ const metadata = await getHeadMetadata(root, session.id, { atHead: session.head });
122
+ if (json) emit({ session, head: session.head, records: records.map(({ canonical, ...record }) => record),
123
+ diagnostics: metadata.diagnostics, record_format: 'canonical-log', managed_log_path: logPath(root, session.id),
124
+ });
125
+ else printRecords(session.id, 'managed', logPath(root, session.id), records.map(({ canonical, origin }) => ({
126
+ ...canonical, source_type: origin.harness, native_session_id: origin.nativeSessionId,
127
+ })));
128
+ return;
129
+ }
130
+ const reader = new TrajectoryReader(values.db ?? resolveCanonicalDbPath());
131
+ try {
132
+ const session = reader.getSession(id);
133
+ if (!session) throw new Error(`No indexed session ${id}. Use show <central-id> --store <directory> for an unindexed managed session.`);
134
+ const records = reader.getSessionRecords(id);
135
+ if (json) emit({ session, records, record_format: 'ledger-db',
136
+ ...(session.source === 'managed' ? { managed_log_path: session.source_path } : {}),
137
+ });
138
+ else printRecords(id, session.source, session.source_path, records);
139
+ } finally { reader.close(); }
140
+ return;
141
+ }
142
+ if (positionals.length) throw new Error('index accepts no positional arguments.');
143
+ const root = await realpath(storeRoot(values.store));
144
+ const sessions = await sessionsAtRead(root);
145
+ if (!(await stat(join(root, 'format.json'))).isFile()) throw new Error('Managed store format marker is missing.');
146
+ // Validate all selected immutable heads before opening a writer. Keep one
147
+ // conversation in memory at a time, including complete tool output.
148
+ for (const session of sessions) {
149
+ await history(root, session);
150
+ await getHeadMetadata(root, session.id, { atHead: session.head });
151
+ }
152
+ const dbPath = values.db ?? resolveCanonicalDbPath();
153
+ if (!isMemoryStorePath(dbPath)) {
154
+ if (!values.db && !process.env.TRAJECTORIES_DB && !process.env.LEDGER_DB) ensurePrivateDirectory(dirname(dbPath));
155
+ else await mkdir(dirname(dbPath), { recursive: true, mode: 0o700 });
156
+ }
157
+ const lease = isMemoryStorePath(dbPath) ? null : acquireWriterLease(dbPath, { holder: 'trajectories index' });
158
+ try {
159
+ const db = new TrajectoryDb(dbPath);
160
+ try {
161
+ const results: Array<{ session_id: string; head: string; outcome: string; records: number }> = [];
162
+ for (const session of sessions) {
163
+ const id = `managed:${session.id}`;
164
+ if (db.getTombstone(id)) { results.push({ session_id: id, head: session.head, outcome: 'deleted', records: 0 }); continue; }
165
+ const accepted = await history(root, session);
166
+ const metadata = await getHeadMetadata(root, session.id, { atHead: session.head });
167
+ const projected = project(session, accepted, root, metadata);
168
+ const saved = db.saveSession(projected.session, projected.records, aggregateIngestDiagnostics(metadata.diagnostics));
169
+ results.push({ session_id: id, head: session.head, outcome: saved.inserted ? 'inserted' : saved.updated ? 'updated' : 'unchanged', records: projected.records.length });
170
+ }
171
+ if (json) emit({ store: root, db: dbPath, sessions: results, embeddings: 'not generated; lexical index only' });
172
+ else {
173
+ for (const result of results) console.log(`${result.outcome}: ${result.session_id} (${result.records} records; head ${result.head})`);
174
+ console.log(`Indexed ${results.length} managed sessions in ${dbPath}.`);
175
+ }
176
+ } finally { db.close(); }
177
+ } finally { lease?.close(); }
178
+ }
@@ -0,0 +1,68 @@
1
+ import { closeSync, fstatSync, openSync, readSync } from "node:fs";
2
+ import type { Diagnostic } from "@letta-ai/trajectory";
3
+ import type { ParentageDiagnostic } from "../parentage.js";
4
+
5
+ export const MAX_NATIVE_BYTES = 64 * 1024 * 1024;
6
+ export const MAX_NATIVE_RECORDS = 100_000;
7
+
8
+ /** A deterministic input/schema/budget failure, not a transient database or filesystem error. */
9
+ export class NativeInputError extends Error {}
10
+
11
+ export interface NativeReadOptions { maxBytes?: number }
12
+ export interface NativeDiscoveryOptions {
13
+ limit: number;
14
+ /** Select a bounded discovery page after native deduplication, without decoding bodies. */
15
+ select?: (items: import("../types.js").DiscoveredSession[]) => import("../types.js").DiscoveredSession[];
16
+ onDiagnostic?: (diagnostic: ParentageDiagnostic) => void;
17
+ }
18
+ export interface NativeTranscript {
19
+ transcript: string;
20
+ title?: string;
21
+ cwd?: string;
22
+ model?: string;
23
+ startedAt?: string;
24
+ endedAt?: string;
25
+ /**
26
+ * Input the reader dropped while assembling, in the normalizer's own
27
+ * vocabulary: the session's stored diagnostics are the library's plus these,
28
+ * so a reader's typed skips reach coverage instead of vanishing.
29
+ */
30
+ diagnostics?: Diagnostic[];
31
+ }
32
+
33
+ export function nativeByteLimit(options: NativeReadOptions = {}): number {
34
+ const limit = options.maxBytes ?? MAX_NATIVE_BYTES;
35
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("Native input byte limit must be positive.");
36
+ return limit;
37
+ }
38
+
39
+ export function readBoundedText(path: string, maxBytes = MAX_NATIVE_BYTES): string {
40
+ const fd = openSync(path, "r");
41
+ try {
42
+ const stat = fstatSync(fd);
43
+ if (!stat.isFile() || stat.size > maxBytes) throw new NativeInputError("Native input exceeds the byte limit or is not a file.");
44
+ const buffer = Buffer.alloc(stat.size);
45
+ let offset = 0;
46
+ while (offset < buffer.length) {
47
+ const read = readSync(fd, buffer, offset, buffer.length - offset, offset);
48
+ if (!read) throw new Error("Native input changed while reading.");
49
+ offset += read;
50
+ }
51
+ if (readSync(fd, Buffer.alloc(1), 0, 1, offset)) throw new Error("Native input changed while reading.");
52
+ return buffer.toString("utf8").replace(/^\uFEFF/, "");
53
+ } finally {
54
+ closeSync(fd);
55
+ }
56
+ }
57
+
58
+ export function object(value: unknown): value is Record<string, unknown> {
59
+ return value !== null && typeof value === "object" && !Array.isArray(value);
60
+ }
61
+
62
+ export function safeNativeId(value: unknown): value is string {
63
+ return typeof value === "string" && value.trim().length > 0 && value !== "." && value !== ".." && !/[\x00-\x1f/\\]/.test(value);
64
+ }
65
+
66
+ export function reportNativeError(options: NativeDiscoveryOptions, sourcePath: string): void {
67
+ options.onDiagnostic?.({ code: "read_error", sourcePath, message: "Native history could not be read; coverage may be incomplete." });
68
+ }