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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +201 -0
- package/README.md +288 -2
- package/dist/cli.js +30210 -0
- package/dist/convert.d.ts +31 -0
- package/dist/convert.js +431 -0
- package/dist/deepagents_checkpoint.py +362 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8402 -0
- package/dist/log-baseline.d.ts +26 -0
- package/dist/log-baseline.js +104 -0
- package/dist/log-content.d.ts +16 -0
- package/dist/log-content.js +80 -0
- package/dist/log-io.d.ts +10 -0
- package/dist/log-io.js +121 -0
- package/dist/log-read.d.ts +24 -0
- package/dist/log-read.js +117 -0
- package/dist/log-reconcile.d.ts +5 -0
- package/dist/log-reconcile.js +38 -0
- package/dist/log-records.d.ts +35 -0
- package/dist/log-records.js +85 -0
- package/dist/log-recovery.d.ts +18 -0
- package/dist/log-recovery.js +123 -0
- package/dist/log-stage.d.ts +14 -0
- package/dist/log-stage.js +138 -0
- package/dist/log-state.d.ts +89 -0
- package/dist/log-state.js +92 -0
- package/dist/log-switch.d.ts +3 -0
- package/dist/log-switch.js +98 -0
- package/dist/log-types.d.ts +129 -0
- package/dist/log-types.js +8 -0
- package/dist/log.d.ts +8 -0
- package/dist/log.js +62 -0
- package/dist/migrate.d.ts +29 -0
- package/dist/migrate.js +173 -0
- package/dist/normalization-runtime.d.ts +1 -0
- package/dist/normalization-runtime.js +3150 -0
- package/dist/portable.d.ts +9 -0
- package/dist/portable.js +26 -0
- package/dist/readers/canonical.d.ts +13 -0
- package/dist/readers/canonical.js +54 -0
- package/dist/readers/cline.d.ts +10 -0
- package/dist/readers/cline.js +107 -0
- package/dist/readers/cursor.d.ts +20 -0
- package/dist/readers/cursor.js +374 -0
- package/dist/readers/deepagents.d.ts +41 -0
- package/dist/readers/deepagents.js +163 -0
- package/dist/readers/fx.d.ts +11 -0
- package/dist/readers/fx.js +573 -0
- package/dist/readers/github-copilot.d.ts +8 -0
- package/dist/readers/github-copilot.js +216 -0
- package/dist/readers/grok-build.d.ts +12 -0
- package/dist/readers/grok-build.js +391 -0
- package/dist/readers/guards.d.ts +4 -0
- package/dist/readers/guards.js +121 -0
- package/dist/readers/pi-compaction.d.ts +7 -0
- package/dist/readers/pi-compaction.js +112 -0
- package/dist/readers/pi-context.d.ts +13 -0
- package/dist/readers/pi-context.js +64 -0
- package/dist/store.d.ts +25 -0
- package/dist/store.js +218 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/dist/writers/claude-code.d.ts +5 -0
- package/dist/writers/claude-code.js +51 -0
- package/dist/writers/cline.d.ts +3 -0
- package/dist/writers/cline.js +77 -0
- package/dist/writers/codex.d.ts +3 -0
- package/dist/writers/codex.js +32 -0
- package/dist/writers/cursor.d.ts +3 -0
- package/dist/writers/cursor.js +197 -0
- package/dist/writers/deepagents.d.ts +3 -0
- package/dist/writers/deepagents.js +116 -0
- package/dist/writers/entropy.d.ts +5 -0
- package/dist/writers/entropy.js +33 -0
- package/dist/writers/fx.d.ts +3 -0
- package/dist/writers/fx.js +135 -0
- package/dist/writers/github-copilot.d.ts +3 -0
- package/dist/writers/github-copilot.js +71 -0
- package/dist/writers/grok-build.d.ts +3 -0
- package/dist/writers/grok-build.js +43 -0
- package/dist/writers/helpers.d.ts +17 -0
- package/dist/writers/helpers.js +75 -0
- package/dist/writers/omp.d.ts +3 -0
- package/dist/writers/omp.js +7 -0
- package/dist/writers/opencode.d.ts +3 -0
- package/dist/writers/opencode.js +65 -0
- package/dist/writers/pi.d.ts +3 -0
- package/dist/writers/pi.js +45 -0
- package/docs/cline-native-format.md +43 -0
- package/docs/copilot-native-format.md +78 -0
- package/docs/cursor-native-format.md +103 -0
- package/docs/deepagents-native-format.md +98 -0
- package/docs/evidence-api.md +170 -0
- package/docs/fx-native-format.md +87 -0
- package/docs/harness-parity-bootstrap.md +78 -0
- package/docs/harness-support-parity.md +107 -0
- package/docs/inspirations.md +103 -0
- package/docs/ledger-migration.md +141 -0
- package/docs/pi-compaction-context.md +75 -0
- package/docs/pi-provider-context.md +76 -0
- package/docs/research/2026-10-06-huihua-and-oar.md +176 -0
- package/docs/research/ledger/README.md +96 -0
- package/docs/research/ledger/confer/2026-09-13-round-10/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-2/cass.md +72 -0
- package/docs/research/ledger/confer/2026-09-13-round-3/README.md +70 -0
- package/docs/research/ledger/confer/2026-09-13-round-5/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-6/README.md +68 -0
- package/docs/research/ledger/confer/2026-09-13-round-7/README.md +64 -0
- package/docs/research/ledger/confer/2026-09-13-round-9/README.md +59 -0
- package/docs/research/ledger/confer/2026-09-14-wake/wake.md +146 -0
- package/docs/research/ledger/docs/inspirations.md +320 -0
- package/docs/research/ledger/docs/pr2-pr6-integration.md +53 -0
- package/docs/research/ledger/docs/research/2026-09-07-confer-inspirations.md +163 -0
- package/docs/research/ledger/docs/research/2026-09-07-letta-continuous-service.md +104 -0
- package/docs/research/ledger/docs/research/2026-09-08-claude-history-retrieval.md +117 -0
- package/docs/research/ledger/docs/research/2026-09-09-agentsview-native-discovery.md +87 -0
- package/docs/research/ledger/docs/research/2026-09-09-daemon-observation.md +231 -0
- package/docs/research/ledger/docs/research/2026-09-09-discovery-pagination.md +114 -0
- package/docs/research/ledger/docs/research/2026-09-09-harness-transcript-contract.md +145 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-discovery.md +180 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-implementation.md +137 -0
- package/docs/research/ledger/docs/research/2026-09-09-other-inspirations.md +279 -0
- package/docs/research/ledger/docs/research/2026-09-09-trajectory-and-letta-code.md +128 -0
- package/docs/research/ledger/docs/research/2026-09-13-diagnostics-coverage.md +116 -0
- package/docs/research/ledger/docs/research/2026-09-13-fts-rebuild.md +83 -0
- package/docs/research/ledger/docs/research/2026-09-13-read-path-split.md +74 -0
- package/docs/research/ledger/docs/research/2026-09-13-runtime-rollout.md +102 -0
- package/docs/research/ledger/docs/research/2026-09-13-scan-outcomes.md +80 -0
- package/docs/research/ledger/docs/research/2026-09-13-search-quality.md +105 -0
- package/docs/research/ledger/docs/research/2026-09-13-source-coverage.md +64 -0
- package/docs/research/ledger/docs/research/2026-09-13-sync-wall-budget.md +82 -0
- package/docs/research/ledger/docs/research/2026-09-13-typed-read-gate.md +81 -0
- package/docs/research/ledger/docs/research/2026-09-13-writer-lease.md +84 -0
- package/docs/research/ledger/docs/research/2026-09-16-c1-copy-fallback.md +227 -0
- package/docs/research/ledger/docs/research/2026-09-16-c10-kimi.md +414 -0
- package/docs/research/ledger/docs/research/2026-09-16-c2-derive-counter.md +216 -0
- package/docs/research/ledger/docs/research/2026-09-16-c3-echo-filter.md +305 -0
- package/docs/research/ledger/docs/research/2026-09-16-c4-title-index.md +349 -0
- package/docs/research/ledger/docs/research/2026-09-16-c5-trigram.md +268 -0
- package/docs/research/ledger/docs/research/2026-09-16-c6-recency-prior.md +438 -0
- package/docs/research/ledger/docs/research/2026-09-16-c7-tombstones.md +467 -0
- package/docs/research/ledger/docs/research/2026-09-16-c8-remote-truth.md +390 -0
- package/docs/research/ledger/docs/research/2026-09-16-c9-grok.md +380 -0
- package/docs/research/ledger/docs/research/2026-09-16-wake-port-program.md +38 -0
- package/docs/research/ledger/docs/research/2026-09-17-cursor-blobstore.md +157 -0
- package/docs/research/ledger/manifest.json +540 -0
- package/legacy/NOTICE.md +50 -0
- package/legacy/bin/ledger-core.ts +7 -0
- package/legacy/bin/ledger.ts +7 -0
- package/legacy/bin/traj-bridge.ts +7 -0
- package/legacy/scripts/import-claude-history.ts +130 -0
- package/legacy/scripts/native-history-smoke.ts +52 -0
- package/legacy/scripts/observe-daemon.ts +570 -0
- package/legacy/scripts/retitle-sessions.md +19 -0
- package/legacy/scripts/verify-runtime-rollout.ts +151 -0
- package/legacy/src/canonical.ts +108 -0
- package/legacy/src/cli.ts +1293 -0
- package/legacy/src/db.ts +2254 -0
- package/legacy/src/diagnostics.ts +87 -0
- package/legacy/src/discovery.ts +56 -0
- package/legacy/src/embeddings.ts +382 -0
- package/legacy/src/filter.ts +602 -0
- package/legacy/src/gemini_embeddings.ts +103 -0
- package/legacy/src/identity.ts +9 -0
- package/legacy/src/index.ts +10 -0
- package/legacy/src/ingest.ts +791 -0
- package/legacy/src/managed.ts +178 -0
- package/legacy/src/native/common.ts +68 -0
- package/legacy/src/native/cursor.ts +647 -0
- package/legacy/src/native/grok.ts +540 -0
- package/legacy/src/native/kimi.ts +706 -0
- package/legacy/src/native/opencode.ts +470 -0
- package/legacy/src/native/sqlite-ro.ts +281 -0
- package/legacy/src/native.ts +97 -0
- package/legacy/src/normalizer.ts +457 -0
- package/legacy/src/parentage.ts +562 -0
- package/legacy/src/pressure.ts +162 -0
- package/legacy/src/private-storage.ts +74 -0
- package/legacy/src/reader.ts +1255 -0
- package/legacy/src/scanner.ts +591 -0
- package/legacy/src/schema-gate.ts +263 -0
- package/legacy/src/schema.ts +829 -0
- package/legacy/src/semantic-top-k.ts +71 -0
- package/legacy/src/session-hash.ts +49 -0
- package/legacy/src/store-status.ts +218 -0
- package/legacy/src/sync-preparation.ts +45 -0
- package/legacy/src/sync-truth.ts +843 -0
- package/legacy/src/sync.ts +1233 -0
- package/legacy/src/types.ts +436 -0
- package/legacy/src/vocab.txt +30522 -0
- package/legacy/src/watch.ts +673 -0
- package/legacy/src/writer-lease.ts +310 -0
- package/ops/README.md +150 -0
- package/ops/install-cli.sh +63 -0
- package/ops/install-native.sh +35 -0
- package/ops/install-release.sh +49 -0
- package/ops/linux/install-systemd-user.sh +103 -0
- package/ops/macos/install-launchd.sh +72 -0
- package/ops/sync-settings.sh +37 -0
- package/package.json +21 -5
- package/skills/trajectories/SKILL.md +138 -0
- package/skills/trajectories/scripts/trajectories_recent.sh +26 -0
- package/skills/trajectories/scripts/trajectories_search.sh +26 -0
- package/index.js +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, realpath } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { HARNESSES } from './types.js';
|
|
5
|
+
import { TrajectoriesError } from './log-types.js';
|
|
6
|
+
import { convertManaged, readNativeExportSnapshot, sha256, writeNativeExport } from './convert.js';
|
|
7
|
+
import { digest, stableJson, withSessionLock } from './log-io.js';
|
|
8
|
+
import { putText } from './log-content.js';
|
|
9
|
+
import { initializeLog, publishFrame, readFrames } from './log-state.js';
|
|
10
|
+
import { exportRecords, loadStage, MANAGED_CODEC_VERSION, recoverSnapshot } from './log-recovery.js';
|
|
11
|
+
import { NATIVE_ENTROPY_VERSION } from './writers/entropy.js';
|
|
12
|
+
import { readIntent, saveIntent } from './log-stage.js';
|
|
13
|
+
import { receiptOf } from './log-read.js';
|
|
14
|
+
const manifestOf = (native) => ({ ...native, files: native.files.map(file => ({ path: file.path,
|
|
15
|
+
sha256: sha256(file.content), sizeBytes: Buffer.byteLength(file.content) })) });
|
|
16
|
+
async function preparedReceipt(store, frame) {
|
|
17
|
+
if (!frame.body.recipe)
|
|
18
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Prepared binding recipe is missing.');
|
|
19
|
+
return { ...await receiptOf(store, frame), binding: frame.body.binding, native: frame.body.recipe.native };
|
|
20
|
+
}
|
|
21
|
+
export async function switchSession(store, id, target, requested) {
|
|
22
|
+
if (!requested || typeof requested !== 'object' || !requested.write || typeof requested.write !== 'object')
|
|
23
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Export options are required.');
|
|
24
|
+
if (!HARNESSES.includes(target))
|
|
25
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Unsupported target harness.');
|
|
26
|
+
let cwd;
|
|
27
|
+
try {
|
|
28
|
+
cwd = await realpath(resolve(requested.write.cwd));
|
|
29
|
+
}
|
|
30
|
+
catch (cause) {
|
|
31
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Export workspace must exist.', { cause });
|
|
32
|
+
}
|
|
33
|
+
const write = { ...requested.write, directory: resolve(requested.write.directory), cwd };
|
|
34
|
+
const input = digest(stableJson({ operation: 'switch', id, target, expectedHead: requested.expectedHead, intentId: requested.intentId, write }));
|
|
35
|
+
return withSessionLock(store, id, async () => {
|
|
36
|
+
await initializeLog(store);
|
|
37
|
+
const frames = await readFrames(store, id), current = frames.at(-1);
|
|
38
|
+
if (!current)
|
|
39
|
+
throw new TrajectoriesError('NOT_FOUND', 'Managed session does not exist.');
|
|
40
|
+
const existing = await readIntent(store, id, requested.intentId);
|
|
41
|
+
if (existing && existing.input !== input)
|
|
42
|
+
throw new TrajectoriesError('INTENT_CONFLICT', 'This intent is bound to different export input or options.');
|
|
43
|
+
const committed = frames.find(frame => frame.body.intentId === requested.intentId);
|
|
44
|
+
if (committed) {
|
|
45
|
+
if (committed.body.input !== input)
|
|
46
|
+
throw new TrajectoriesError('INTENT_CONFLICT', 'Committed intent has different export input.');
|
|
47
|
+
if (current.head !== committed.head)
|
|
48
|
+
throw new TrajectoriesError('STALE_HEAD', 'Prepared child already has a successor.');
|
|
49
|
+
return preparedReceipt(store, committed);
|
|
50
|
+
}
|
|
51
|
+
if (current.head !== requested.expectedHead)
|
|
52
|
+
throw new TrajectoriesError('STALE_HEAD', 'Cannot prepare a continuation from a stale head.');
|
|
53
|
+
if (existing)
|
|
54
|
+
throw new TrajectoriesError('OUTCOME_UNKNOWN', 'Export intent has no committed receipt; reconcile its frozen files before admitting execution.');
|
|
55
|
+
if (cwd !== current.body.session.workspace)
|
|
56
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Export must keep the managed workspace.');
|
|
57
|
+
try {
|
|
58
|
+
await lstat(write.directory);
|
|
59
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Export destination already exists.');
|
|
60
|
+
}
|
|
61
|
+
catch (cause) {
|
|
62
|
+
if (cause.code !== 'ENOENT')
|
|
63
|
+
throw cause;
|
|
64
|
+
}
|
|
65
|
+
const capture = [...frames].reverse().find(frame => frame.body.capture)?.body.capture;
|
|
66
|
+
if (!capture)
|
|
67
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Export source capture is missing.');
|
|
68
|
+
const snapshot = await recoverSnapshot(store, await loadStage(store, capture), frames);
|
|
69
|
+
const frozen = { ...write, sessionId: write.sessionId ?? randomUUID(), timestamp: write.timestamp ?? new Date().toISOString() };
|
|
70
|
+
const seed = digest(stableJson([input, 'native-entropy']));
|
|
71
|
+
let native;
|
|
72
|
+
try {
|
|
73
|
+
native = convertManaged(snapshot, target, frozen, seed, await exportRecords(store, frames, snapshot));
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
throw new TrajectoriesError('UNSUPPORTED_CONTEXT', cause instanceof Error ? cause.message : 'Export conversion failed.', { cause });
|
|
77
|
+
}
|
|
78
|
+
const baseline = readNativeExportSnapshot(native);
|
|
79
|
+
if (baseline.source.nativeSessionId !== native.sessionId)
|
|
80
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Export failed to establish its native identity.');
|
|
81
|
+
const recipe = { version: 1, codec: MANAGED_CODEC_VERSION, entropy: NATIVE_ENTROPY_VERSION, seed,
|
|
82
|
+
atHead: current.head, write: frozen, native: manifestOf(native) };
|
|
83
|
+
// Freeze identity/entropy and every file fingerprint durably before writing any native file.
|
|
84
|
+
await saveIntent(store, id, requested.intentId, { input, export: recipe });
|
|
85
|
+
try {
|
|
86
|
+
await writeNativeExport(native, frozen.directory);
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
throw new TrajectoriesError('OUTCOME_UNKNOWN', 'Native export was interrupted; no binding was published.', { cause });
|
|
90
|
+
}
|
|
91
|
+
const binding = { id: `b_${digest(stableJson([id, requested.intentId]))}`, harness: target, nativeSessionId: native.sessionId };
|
|
92
|
+
const meta = baseline.canonical.records.find(record => record.record_type === 'meta');
|
|
93
|
+
const body = { parent: current.head, intentId: requested.intentId, input, time: new Date().toISOString(), kind: 'binding',
|
|
94
|
+
session: current.body.session, records: [], binding, progress: false, recipe,
|
|
95
|
+
diagnostics: await putText(store, stableJson(native.diagnostics)), ...(meta ? { nativeMeta: JSON.parse(meta.record_json) } : {}) };
|
|
96
|
+
return preparedReceipt(store, await publishFrame(store, body));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { NormalizedRecord } from '@letta-ai/trajectory';
|
|
2
|
+
import type { ConversionDiagnostic, Harness, NativeExport, Snapshot, WriteOptions } from './types.js';
|
|
3
|
+
export type Store = string;
|
|
4
|
+
export type SessionId = string;
|
|
5
|
+
export type IntentId = string;
|
|
6
|
+
export type Head = string & {
|
|
7
|
+
readonly __head: unique symbol;
|
|
8
|
+
};
|
|
9
|
+
export type BindingId = string & {
|
|
10
|
+
readonly __binding: unique symbol;
|
|
11
|
+
};
|
|
12
|
+
export type CaptureId = string & {
|
|
13
|
+
readonly __capture: unique symbol;
|
|
14
|
+
};
|
|
15
|
+
export type RecordId = string & {
|
|
16
|
+
readonly __record: unique symbol;
|
|
17
|
+
};
|
|
18
|
+
export type PageCursor = string & {
|
|
19
|
+
readonly __cursor: unique symbol;
|
|
20
|
+
};
|
|
21
|
+
export interface NativeCheckpointLimits {
|
|
22
|
+
maxCount: number;
|
|
23
|
+
maxBytes: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ManagedSession {
|
|
26
|
+
version: 2;
|
|
27
|
+
id: SessionId;
|
|
28
|
+
workspace: string;
|
|
29
|
+
head: Head;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
nativeCheckpoints: NativeCheckpointLimits;
|
|
33
|
+
}
|
|
34
|
+
export interface CaptureRef {
|
|
35
|
+
id: CaptureId;
|
|
36
|
+
sessionId: SessionId;
|
|
37
|
+
parent: Head | null;
|
|
38
|
+
intentId: IntentId;
|
|
39
|
+
bindingId?: BindingId;
|
|
40
|
+
integrity: string;
|
|
41
|
+
source: Pick<Snapshot['source'], 'harness' | 'nativeSessionId' | 'sha256'>;
|
|
42
|
+
}
|
|
43
|
+
export interface CommitReceipt {
|
|
44
|
+
intentId: IntentId;
|
|
45
|
+
session: ManagedSession;
|
|
46
|
+
parent: Head | null;
|
|
47
|
+
diagnostics: readonly ConversionDiagnostic[];
|
|
48
|
+
capture?: CaptureRef;
|
|
49
|
+
}
|
|
50
|
+
export interface PreparedExport extends Omit<NativeExport, 'files'> {
|
|
51
|
+
files: readonly {
|
|
52
|
+
path: string;
|
|
53
|
+
sha256: string;
|
|
54
|
+
sizeBytes: number;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
57
|
+
export interface NativeBinding {
|
|
58
|
+
id: BindingId;
|
|
59
|
+
harness: Harness;
|
|
60
|
+
nativeSessionId: string;
|
|
61
|
+
}
|
|
62
|
+
export interface PreparedSession extends CommitReceipt {
|
|
63
|
+
binding: NativeBinding;
|
|
64
|
+
native: PreparedExport;
|
|
65
|
+
}
|
|
66
|
+
export interface ConversationRecord {
|
|
67
|
+
id: RecordId;
|
|
68
|
+
payload: NormalizedRecord;
|
|
69
|
+
origin: {
|
|
70
|
+
harness: Harness;
|
|
71
|
+
nativeSessionId?: string;
|
|
72
|
+
bindingId?: BindingId;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export interface Page<T> {
|
|
76
|
+
items: readonly T[];
|
|
77
|
+
nextCursor: PageCursor | null;
|
|
78
|
+
}
|
|
79
|
+
export type StageOptions = {
|
|
80
|
+
intentId: IntentId;
|
|
81
|
+
requireProgress?: boolean;
|
|
82
|
+
} & ({
|
|
83
|
+
sessionId: SessionId;
|
|
84
|
+
expectedHead: null;
|
|
85
|
+
workspace: string;
|
|
86
|
+
nativeCheckpoints?: NativeCheckpointLimits;
|
|
87
|
+
} | {
|
|
88
|
+
sessionId: SessionId;
|
|
89
|
+
expectedHead: Head;
|
|
90
|
+
bindingId?: BindingId;
|
|
91
|
+
});
|
|
92
|
+
export interface CreateOptions {
|
|
93
|
+
id: SessionId;
|
|
94
|
+
workspace: string;
|
|
95
|
+
intentId: IntentId;
|
|
96
|
+
nativeCheckpoints?: NativeCheckpointLimits;
|
|
97
|
+
}
|
|
98
|
+
export interface AppendOptions {
|
|
99
|
+
expectedHead: Head;
|
|
100
|
+
intentId: IntentId;
|
|
101
|
+
bindingId?: BindingId;
|
|
102
|
+
requireProgress?: boolean;
|
|
103
|
+
}
|
|
104
|
+
export interface SwitchOptions {
|
|
105
|
+
expectedHead: Head;
|
|
106
|
+
intentId: IntentId;
|
|
107
|
+
write: WriteOptions;
|
|
108
|
+
}
|
|
109
|
+
export interface ReadOptions {
|
|
110
|
+
atHead: Head;
|
|
111
|
+
view: 'history' | 'context';
|
|
112
|
+
cursor?: PageCursor;
|
|
113
|
+
limit?: number;
|
|
114
|
+
}
|
|
115
|
+
export interface HeadMetadata {
|
|
116
|
+
head: Head;
|
|
117
|
+
parent: Head | null;
|
|
118
|
+
kind: 'capture' | 'binding';
|
|
119
|
+
progress: boolean;
|
|
120
|
+
diagnostics: readonly ConversionDiagnostic[];
|
|
121
|
+
capture?: CaptureRef;
|
|
122
|
+
binding?: NativeBinding;
|
|
123
|
+
nativeMeta?: NormalizedRecord;
|
|
124
|
+
}
|
|
125
|
+
export type ErrorCode = 'INVALID_INPUT' | 'NOT_FOUND' | 'INTEGRITY_ERROR' | 'UNSUPPORTED_CONTEXT' | 'STALE_HEAD' | 'INTENT_CONFLICT' | 'OPERATION_PENDING' | 'OUTCOME_UNKNOWN' | 'CAPACITY_EXCEEDED';
|
|
126
|
+
export declare class TrajectoriesError extends Error {
|
|
127
|
+
readonly code: ErrorCode;
|
|
128
|
+
constructor(code: ErrorCode, message: string, options?: ErrorOptions);
|
|
129
|
+
}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snapshot } from './types.js';
|
|
2
|
+
import type { AppendOptions, CaptureRef, CommitReceipt, CreateOptions } from './log-types.js';
|
|
3
|
+
export { stageCapture } from './log-stage.js';
|
|
4
|
+
export { switchSession } from './log-switch.js';
|
|
5
|
+
export { getSession, listSessions, readSession, readCanonicalSession, getHeadMetadata, getNativeBinding } from './log-read.js';
|
|
6
|
+
export type { CanonicalConversationRecord } from './log-records.js';
|
|
7
|
+
export declare function createSession(store: string, input: Snapshot | CaptureRef, options: CreateOptions): Promise<CommitReceipt>;
|
|
8
|
+
export declare function appendCapture(store: string, id: string, input: Snapshot | CaptureRef, options: AppendOptions): Promise<CommitReceipt>;
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TrajectoriesError } from './log-types.js';
|
|
2
|
+
import { getObject, stableJson, withSessionLock } from './log-io.js';
|
|
3
|
+
import { initializeLog, publishFrame, readFrames } from './log-state.js';
|
|
4
|
+
import { loadStage } from './log-recovery.js';
|
|
5
|
+
import { normalizeStageOptions, readIntent, stageCapture, validateStageBacking } from './log-stage.js';
|
|
6
|
+
import { receiptOf, selection } from './log-read.js';
|
|
7
|
+
export { stageCapture } from './log-stage.js';
|
|
8
|
+
export { switchSession } from './log-switch.js';
|
|
9
|
+
export { getSession, listSessions, readSession, readCanonicalSession, getHeadMetadata, getNativeBinding } from './log-read.js';
|
|
10
|
+
async function commitCapture(store, ref, requested) {
|
|
11
|
+
const options = await normalizeStageOptions(requested);
|
|
12
|
+
if (ref.sessionId !== options.sessionId || ref.intentId !== options.intentId || ref.parent !== options.expectedHead)
|
|
13
|
+
throw new TrajectoriesError('INTENT_CONFLICT', 'Capture reference does not belong to this session mutation.');
|
|
14
|
+
return withSessionLock(store, options.sessionId, async () => {
|
|
15
|
+
await initializeLog(store);
|
|
16
|
+
const stage = await loadStage(store, ref);
|
|
17
|
+
if (stableJson(stage.options) !== stableJson(options))
|
|
18
|
+
throw new TrajectoriesError('INTENT_CONFLICT', 'Commit options differ from the staged intent.');
|
|
19
|
+
const intent = await readIntent(store, options.sessionId, options.intentId);
|
|
20
|
+
if (!intent || intent.input !== stage.input || !intent.stage || stableJson(JSON.parse((await getObject(store, intent.stage)).toString())) !== stableJson(stage))
|
|
21
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'The staged capture has no matching durable intent binding.');
|
|
22
|
+
const frames = await readFrames(store, options.sessionId), current = frames.at(-1);
|
|
23
|
+
const committed = frames.find(frame => frame.body.intentId === options.intentId);
|
|
24
|
+
if (committed) {
|
|
25
|
+
if (committed.body.input !== stage.input)
|
|
26
|
+
throw new TrajectoriesError('INTENT_CONFLICT', 'Committed intent has different input.');
|
|
27
|
+
if (current.head !== committed.head)
|
|
28
|
+
throw new TrajectoriesError('STALE_HEAD', 'An acknowledged or unacknowledged child already has a successor.');
|
|
29
|
+
await validateStageBacking(store, stage, frames);
|
|
30
|
+
return receiptOf(store, committed);
|
|
31
|
+
}
|
|
32
|
+
if ((current?.head ?? null) !== options.expectedHead)
|
|
33
|
+
throw new TrajectoriesError('STALE_HEAD', 'Session head changed before capture commit.');
|
|
34
|
+
await validateStageBacking(store, stage, frames);
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
const body = {
|
|
37
|
+
parent: options.expectedHead, intentId: options.intentId, input: stage.input, time: now, kind: 'capture',
|
|
38
|
+
session: current?.body.session ?? { id: options.sessionId, workspace: options.workspace,
|
|
39
|
+
createdAt: now, nativeCheckpoints: options.nativeCheckpoints },
|
|
40
|
+
records: stage.records, edit: stage.edit, binding: stage.binding, progress: stage.progress,
|
|
41
|
+
diagnostics: stage.diagnostics, ...(stage.nativeMeta ? { nativeMeta: stage.nativeMeta } : {}), capture: ref, stage: intent.stage,
|
|
42
|
+
};
|
|
43
|
+
// Validate the whole selection before appending its commit frame.
|
|
44
|
+
selection([...frames, { head: '', body }]);
|
|
45
|
+
return receiptOf(store, await publishFrame(store, body));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export async function createSession(store, input, options) {
|
|
49
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || !options || typeof options !== 'object')
|
|
50
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Capture and create options are required.');
|
|
51
|
+
const stage = { sessionId: options.id, expectedHead: null, workspace: options.workspace,
|
|
52
|
+
intentId: options.intentId, ...(options.nativeCheckpoints ? { nativeCheckpoints: options.nativeCheckpoints } : {}) };
|
|
53
|
+
const ref = 'integrity' in input ? input : await stageCapture(store, input, stage);
|
|
54
|
+
return commitCapture(store, ref, stage);
|
|
55
|
+
}
|
|
56
|
+
export async function appendCapture(store, id, input, options) {
|
|
57
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || !options || typeof options !== 'object')
|
|
58
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Capture and append options are required.');
|
|
59
|
+
const stage = { sessionId: id, ...options };
|
|
60
|
+
const ref = 'integrity' in input ? input : await stageCapture(store, input, stage);
|
|
61
|
+
return commitCapture(store, ref, stage);
|
|
62
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SessionBinding } from './types.js';
|
|
2
|
+
import type { BindingId, CaptureRef, Head } from './log-types.js';
|
|
3
|
+
export interface MigratedSession {
|
|
4
|
+
id: string;
|
|
5
|
+
head: Head;
|
|
6
|
+
heads: Record<string, Head>;
|
|
7
|
+
bindings: Array<{
|
|
8
|
+
sourceHead: string;
|
|
9
|
+
exportedSnapshotId: string;
|
|
10
|
+
harness: SessionBinding['harness'];
|
|
11
|
+
nativeSessionId: string;
|
|
12
|
+
head: Head;
|
|
13
|
+
bindingId: BindingId;
|
|
14
|
+
}>;
|
|
15
|
+
captures: Record<string, {
|
|
16
|
+
head: Head;
|
|
17
|
+
capture: CaptureRef;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface MigrationManifest {
|
|
21
|
+
version: 2;
|
|
22
|
+
sourceStore: string;
|
|
23
|
+
destinationStore: string;
|
|
24
|
+
sessions: MigratedSession[];
|
|
25
|
+
}
|
|
26
|
+
/** Offline migration. Callers freeze native and Workflow writers before asserting writersStopped. */
|
|
27
|
+
export declare function migrateLegacyStore(source: string, destination: string, options: {
|
|
28
|
+
writersStopped: true;
|
|
29
|
+
}): Promise<MigrationManifest>;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { lstat, mkdir, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { TrajectoriesError } from './log-types.js';
|
|
4
|
+
import { assertPortableSnapshot, snapshotFromEvidence } from './convert.js';
|
|
5
|
+
import { getSession as getLegacySession, getSnapshot, listSessions as listLegacySessions } from './store.js';
|
|
6
|
+
import { appendCapture, createSession, getSession, readSession } from './log.js';
|
|
7
|
+
import { atomicWrite, digest, ensureDirectory, safeId, stableJson, syncDirectory, withSessionLock } from './log-io.js';
|
|
8
|
+
import { putText } from './log-content.js';
|
|
9
|
+
import { initializeLog, publishFrame, readFrames } from './log-state.js';
|
|
10
|
+
import { bindingSnapshot, loadStage, recoverEvidence, recoverSnapshot } from './log-recovery.js';
|
|
11
|
+
import { loadRecord, units } from './log-records.js';
|
|
12
|
+
import { selection } from './log-read.js';
|
|
13
|
+
import { saveIntent } from './log-stage.js';
|
|
14
|
+
import { mapPreparedRecords } from './log-baseline.js';
|
|
15
|
+
function validateSnapshot(snapshot) {
|
|
16
|
+
try {
|
|
17
|
+
assertPortableSnapshot(snapshot);
|
|
18
|
+
if (snapshotFromEvidence({ harness: snapshot.source.harness, raw: snapshot.raw,
|
|
19
|
+
nativeSessionId: snapshot.source.nativeSessionId, attachments: snapshot.attachments }).id !== snapshot.id)
|
|
20
|
+
throw new Error('Legacy canonical history differs from its retained native evidence.');
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
throw new TrajectoriesError('UNSUPPORTED_CONTEXT', cause instanceof Error ? cause.message : 'Unsupported legacy history.', { cause });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function sameNative(a, b) {
|
|
27
|
+
return a.source.harness === b.source.harness && a.source.nativeSessionId === b.source.nativeSessionId;
|
|
28
|
+
}
|
|
29
|
+
async function importBinding(store, session, sourceHead, binding, index, snapshot, parent) {
|
|
30
|
+
validateSnapshot(snapshot);
|
|
31
|
+
if (snapshot.source.harness !== binding.harness || snapshot.source.nativeSessionId !== binding.nativeSessionId)
|
|
32
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Legacy prepared binding disagrees with its retained native identity.');
|
|
33
|
+
const key = digest(stableJson([session.id, index, sourceHead, binding]));
|
|
34
|
+
const intentId = `m_binding_${key}`, bindingId = `b_m_${key}`;
|
|
35
|
+
return withSessionLock(store, session.id, async () => {
|
|
36
|
+
const frames = await readFrames(store, session.id), current = frames.at(-1);
|
|
37
|
+
if (current.head !== parent)
|
|
38
|
+
throw new TrajectoriesError('STALE_HEAD', 'Migration destination changed before binding publication.');
|
|
39
|
+
const selected = selection(frames), byId = new Map(selected.history.map(record => [record.id, record]));
|
|
40
|
+
const context = await Promise.all(selected.context.map(async (id) => ({ id, record: await loadRecord(store, byId.get(id)) })));
|
|
41
|
+
mapPreparedRecords(context, units(snapshot, true));
|
|
42
|
+
const input = digest(stableJson({ operation: 'migrate-binding', sessionId: session.id, parent, sourceHead, binding, snapshot: snapshot.id }));
|
|
43
|
+
await saveIntent(store, session.id, intentId, { input });
|
|
44
|
+
const { raw, canonical, attachments, ...header } = snapshot;
|
|
45
|
+
const evidence = { header, raw: await putText(store, raw), attachments: [] };
|
|
46
|
+
for (const attachment of attachments ?? [])
|
|
47
|
+
evidence.attachments.push({ name: attachment.name, sha256: attachment.sha256,
|
|
48
|
+
text: await putText(store, attachment.content) });
|
|
49
|
+
await recoverEvidence(store, evidence, frames);
|
|
50
|
+
const meta = canonical.records.find(record => record.record_type === 'meta');
|
|
51
|
+
const frame = await publishFrame(store, { parent, intentId, input, time: new Date().toISOString(), kind: 'binding',
|
|
52
|
+
session: current.body.session, records: [], binding: { id: bindingId, harness: binding.harness, nativeSessionId: binding.nativeSessionId },
|
|
53
|
+
progress: false, legacyBaseline: evidence, diagnostics: await putText(store, stableJson(canonical.diagnostics)),
|
|
54
|
+
...(meta ? { nativeMeta: JSON.parse(meta.record_json) } : {}) });
|
|
55
|
+
return { sourceHead, exportedSnapshotId: snapshot.id, harness: binding.harness, nativeSessionId: binding.nativeSessionId,
|
|
56
|
+
head: frame.head, bindingId };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function migrateSession(source, destination, session) {
|
|
60
|
+
safeId(session.id);
|
|
61
|
+
if (!session.snapshots.length || session.snapshots.at(-1) !== session.head || new Set(session.snapshots).size !== session.snapshots.length
|
|
62
|
+
|| session.bindings.some(binding => !session.snapshots.includes(binding.snapshotId)))
|
|
63
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Legacy session has an invalid accepted history or prepared source head.');
|
|
64
|
+
const migrated = { id: session.id, head: '', heads: {}, bindings: [], captures: {} };
|
|
65
|
+
let observed = await getSnapshot(source, session.snapshots[0]);
|
|
66
|
+
let selectedBinding;
|
|
67
|
+
for (const [index, oldHead] of session.snapshots.entries()) {
|
|
68
|
+
validateSnapshot(observed);
|
|
69
|
+
const intentId = `m_capture_${oldHead}`;
|
|
70
|
+
const receipt = index === 0
|
|
71
|
+
? await createSession(destination, observed, { id: session.id, workspace: session.workspace, intentId })
|
|
72
|
+
: await appendCapture(destination, session.id, observed, { expectedHead: migrated.head, intentId,
|
|
73
|
+
...(selectedBinding ? { bindingId: selectedBinding } : {}) });
|
|
74
|
+
migrated.head = receipt.session.head;
|
|
75
|
+
migrated.captures[oldHead] = { head: receipt.session.head, capture: receipt.capture };
|
|
76
|
+
const nextId = session.snapshots[index + 1], next = nextId ? await getSnapshot(source, nextId) : undefined;
|
|
77
|
+
const prepared = session.bindings.map((binding, ordinal) => ({ binding, ordinal })).filter(item => item.binding.snapshotId === oldHead);
|
|
78
|
+
const selected = next && !sameNative(observed, next) ? prepared.find(item => item.binding.harness === next.source.harness
|
|
79
|
+
&& item.binding.nativeSessionId === next.source.nativeSessionId) : undefined;
|
|
80
|
+
if (next && !sameNative(observed, next) && !selected)
|
|
81
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Legacy native handoff has no prepared binding at its accepted source head.');
|
|
82
|
+
selectedBinding = undefined;
|
|
83
|
+
// The capture's chosen old target must be the current binding at its new parent.
|
|
84
|
+
for (const item of [...prepared.filter(item => item !== selected), ...(selected ? [selected] : [])]) {
|
|
85
|
+
const baseline = await getSnapshot(source, item.binding.exportedSnapshotId);
|
|
86
|
+
const imported = await importBinding(destination, session, oldHead, item.binding, item.ordinal, baseline, migrated.head);
|
|
87
|
+
migrated.bindings.push(imported);
|
|
88
|
+
migrated.head = imported.head;
|
|
89
|
+
if (item === selected)
|
|
90
|
+
selectedBinding = imported.bindingId;
|
|
91
|
+
}
|
|
92
|
+
migrated.heads[oldHead] = migrated.head;
|
|
93
|
+
if (next)
|
|
94
|
+
observed = next;
|
|
95
|
+
}
|
|
96
|
+
const frames = await readFrames(destination, session.id);
|
|
97
|
+
for (const [oldId, captured] of Object.entries(migrated.captures)) {
|
|
98
|
+
const recovered = await recoverSnapshot(destination, await loadStage(destination, captured.capture), frames);
|
|
99
|
+
if (recovered.id !== oldId)
|
|
100
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Migrated capture identity changed during reconstruction.');
|
|
101
|
+
}
|
|
102
|
+
for (const binding of migrated.bindings)
|
|
103
|
+
if ((await bindingSnapshot(destination, frames, binding.bindingId)).id !== binding.exportedSnapshotId)
|
|
104
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', 'Migrated prepared baseline identity changed during reconstruction.');
|
|
105
|
+
let cursor;
|
|
106
|
+
do {
|
|
107
|
+
const page = await readSession(destination, session.id, { atHead: migrated.head, view: 'history', cursor, limit: 1000 });
|
|
108
|
+
cursor = page.nextCursor ?? undefined;
|
|
109
|
+
} while (cursor);
|
|
110
|
+
if ((await getSession(destination, session.id)).head !== migrated.head)
|
|
111
|
+
throw new TrajectoriesError('STALE_HEAD', 'Migration destination changed before verification.');
|
|
112
|
+
return migrated;
|
|
113
|
+
}
|
|
114
|
+
async function withSourceLocks(source, ids, action, offset = 0) {
|
|
115
|
+
return offset === ids.length ? action() : withSessionLock(source, ids[offset], () => withSourceLocks(source, ids, action, offset + 1));
|
|
116
|
+
}
|
|
117
|
+
/** Offline migration. Callers freeze native and Workflow writers before asserting writersStopped. */
|
|
118
|
+
export async function migrateLegacyStore(source, destination, options) {
|
|
119
|
+
if (options?.writersStopped !== true)
|
|
120
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Stop all native and Workflow writers before migration; writersStopped must be true.');
|
|
121
|
+
if (typeof source !== 'string' || !source || typeof destination !== 'string' || !destination)
|
|
122
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Migration source and destination paths are required.');
|
|
123
|
+
try {
|
|
124
|
+
const sourceStore = await realpath(resolve(source)), destinationPath = resolve(destination);
|
|
125
|
+
try {
|
|
126
|
+
await lstat(destinationPath);
|
|
127
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Migration destination must be fresh; existing and partial destinations are preserved.');
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
if (cause.code !== 'ENOENT')
|
|
131
|
+
throw cause;
|
|
132
|
+
}
|
|
133
|
+
if (!(await lstat(join(sourceStore, 'sessions'))).isDirectory())
|
|
134
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Migration source must be a version-1 session store.');
|
|
135
|
+
try {
|
|
136
|
+
const format = JSON.parse(await readFile(join(sourceStore, 'format.json'), 'utf8'));
|
|
137
|
+
if (format.version !== 1)
|
|
138
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Migration source must use version 1.');
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
if (cause.code !== 'ENOENT')
|
|
142
|
+
throw cause;
|
|
143
|
+
}
|
|
144
|
+
const initial = await listLegacySessions(sourceStore), ids = initial.map(session => session.id).sort();
|
|
145
|
+
return await withSourceLocks(sourceStore, ids, async () => {
|
|
146
|
+
const sessions = await Promise.all(ids.map(id => getLegacySession(sourceStore, id)));
|
|
147
|
+
if (stableJson((await listLegacySessions(sourceStore)).map(session => session.id).sort()) !== stableJson(ids))
|
|
148
|
+
throw new TrajectoriesError('OPERATION_PENDING', 'Legacy session inventory changed while acquiring migration locks.');
|
|
149
|
+
await ensureDirectory(dirname(destinationPath));
|
|
150
|
+
try {
|
|
151
|
+
await mkdir(destinationPath, { mode: 0o700 });
|
|
152
|
+
}
|
|
153
|
+
catch (cause) {
|
|
154
|
+
if (cause.code === 'EEXIST')
|
|
155
|
+
throw new TrajectoriesError('INVALID_INPUT', 'Migration destination already exists.', { cause });
|
|
156
|
+
throw cause;
|
|
157
|
+
}
|
|
158
|
+
await syncDirectory(dirname(destinationPath));
|
|
159
|
+
const destinationStore = await realpath(destinationPath);
|
|
160
|
+
await initializeLog(destinationStore);
|
|
161
|
+
const manifest = { version: 2, sourceStore, destinationStore, sessions: [] };
|
|
162
|
+
for (const session of sessions)
|
|
163
|
+
manifest.sessions.push(await migrateSession(sourceStore, destinationStore, session));
|
|
164
|
+
await atomicWrite(join(destinationStore, 'migration.json'), stableJson(manifest) + '\n', true);
|
|
165
|
+
return manifest;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (cause) {
|
|
169
|
+
if (cause instanceof TrajectoriesError)
|
|
170
|
+
throw cause;
|
|
171
|
+
throw new TrajectoriesError('INTEGRITY_ERROR', cause instanceof Error ? cause.message : 'Legacy migration failed; preserve the source and partial destination.', { cause });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NormalizationError, normalizeToCanonical, normalizeTranscript, } from '@letta-ai/trajectory';
|