praxis-agent 0.35.0 → 0.37.0

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 (111) hide show
  1. package/README.md +20 -2
  2. package/dist/application/background-agent-manager.d.ts +16 -6
  3. package/dist/application/background-agent-manager.js +139 -97
  4. package/dist/application/context-engine.d.ts +56 -0
  5. package/dist/application/context-engine.js +129 -0
  6. package/dist/application/durable-follow-up.d.ts +14 -0
  7. package/dist/application/durable-follow-up.js +52 -0
  8. package/dist/application/native-session-transcript.d.ts +72 -0
  9. package/dist/application/native-session-transcript.js +351 -0
  10. package/dist/application/native-sidechain-transcript.d.ts +34 -0
  11. package/dist/application/native-sidechain-transcript.js +156 -0
  12. package/dist/application/session-service.d.ts +23 -4
  13. package/dist/application/session-service.js +1130 -494
  14. package/dist/application/subagent-service.d.ts +5 -1
  15. package/dist/application/subagent-service.js +749 -320
  16. package/dist/application/team-agent-runtime.d.ts +29 -0
  17. package/dist/application/team-agent-runtime.js +169 -0
  18. package/dist/application/team-capability.d.ts +27 -0
  19. package/dist/application/team-capability.js +41 -0
  20. package/dist/application/team-lead-operations.d.ts +38 -0
  21. package/dist/application/team-lead-operations.js +155 -0
  22. package/dist/application/team-mailbox.d.ts +54 -0
  23. package/dist/application/team-mailbox.js +196 -0
  24. package/dist/application/team-manager.d.ts +113 -0
  25. package/dist/application/team-manager.js +681 -0
  26. package/dist/application/team-workspace.d.ts +29 -0
  27. package/dist/application/team-workspace.js +197 -0
  28. package/dist/application/top-level-agent-manager.d.ts +1 -0
  29. package/dist/application/top-level-agent-manager.js +1025 -403
  30. package/dist/application/transcript-projection.d.ts +35 -0
  31. package/dist/application/transcript-projection.js +139 -0
  32. package/dist/application/turn-lifecycle.d.ts +2 -0
  33. package/dist/application/turn-memory-coordinator.d.ts +43 -0
  34. package/dist/application/turn-memory-coordinator.js +98 -0
  35. package/dist/cli/agents-dashboard.js +10 -5
  36. package/dist/cli/interactive.d.ts +7 -0
  37. package/dist/cli/interactive.js +645 -334
  38. package/dist/cli/tui/claude-style.d.ts +8 -25
  39. package/dist/cli/tui/claude-style.js +299 -277
  40. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  41. package/dist/cli/tui/decision-surface-model.js +131 -0
  42. package/dist/cli/tui/decision-surface.d.ts +7 -0
  43. package/dist/cli/tui/decision-surface.js +41 -0
  44. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  45. package/dist/cli/tui/help-surface-model.js +131 -0
  46. package/dist/cli/tui/mcp-panel.js +1 -2
  47. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  48. package/dist/cli/tui/permission-surface-model.js +360 -0
  49. package/dist/cli/tui/permission-surface.d.ts +7 -0
  50. package/dist/cli/tui/permission-surface.js +69 -0
  51. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  52. package/dist/cli/tui/presentation-environment.js +149 -0
  53. package/dist/cli/tui/task-panel.js +10 -1
  54. package/dist/cli/tui/theme.d.ts +46 -3
  55. package/dist/cli/tui/theme.js +414 -56
  56. package/dist/cli/tui/tool-permission.js +16 -8
  57. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  58. package/dist/cli/tui/transcript-presentation.js +26 -6
  59. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  60. package/dist/cli/tui/transcript-viewport.js +517 -111
  61. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  62. package/dist/cli/tui/transcript-window-model.js +772 -0
  63. package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
  64. package/dist/cli/tui/tui-interaction-router.js +127 -0
  65. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  66. package/dist/cli/tui/tui-screen-model.js +210 -0
  67. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  68. package/dist/cli/tui/tui-view-model.js +1 -68
  69. package/dist/cli-runtime.d.ts +4 -2
  70. package/dist/cli-runtime.js +397 -32
  71. package/dist/compatibility/claude/transcript-codec.js +6 -0
  72. package/dist/core/agent-orchestration.d.ts +31 -0
  73. package/dist/core/agent-orchestration.js +235 -0
  74. package/dist/core/permission-policy.d.ts +4 -0
  75. package/dist/core/permission-policy.js +21 -0
  76. package/dist/core/runtime.d.ts +6 -0
  77. package/dist/core/runtime.js +20 -2
  78. package/dist/core/team-mailbox.d.ts +54 -0
  79. package/dist/core/team-mailbox.js +212 -0
  80. package/dist/core/team-ownership.d.ts +84 -0
  81. package/dist/core/team-ownership.js +505 -0
  82. package/dist/core/tool-execution-scheduler.js +6 -1
  83. package/dist/core/transcript-event.d.ts +5 -1
  84. package/dist/core/transcript-event.js +12 -0
  85. package/dist/persistence/claude-job-store.d.ts +58 -0
  86. package/dist/persistence/claude-job-store.js +483 -1
  87. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  88. package/dist/persistence/claude-transcript-store.js +2 -10
  89. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  90. package/dist/persistence/native-team-mailbox.js +542 -0
  91. package/dist/persistence/native-team-store.d.ts +34 -0
  92. package/dist/persistence/native-team-store.js +257 -0
  93. package/dist/persistence/native-transcript-reader.d.ts +53 -0
  94. package/dist/persistence/native-transcript-reader.js +283 -0
  95. package/dist/persistence/native-transcript-store.d.ts +69 -0
  96. package/dist/persistence/native-transcript-store.js +321 -0
  97. package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
  98. package/dist/persistence/subagent-lifecycle-store.js +373 -146
  99. package/dist/persistence/transcript-file-append.d.ts +2 -0
  100. package/dist/persistence/transcript-file-append.js +10 -0
  101. package/dist/platform/exclusive-file-lease.d.ts +3 -0
  102. package/dist/platform/exclusive-file-lease.js +3 -0
  103. package/dist/tools/claude-capabilities.d.ts +4 -0
  104. package/dist/tools/claude-capabilities.js +18 -2
  105. package/dist/tools/team-lead-tools.d.ts +19 -0
  106. package/dist/tools/team-lead-tools.js +479 -0
  107. package/dist/tools/team-member-tools.d.ts +21 -0
  108. package/dist/tools/team-member-tools.js +259 -0
  109. package/package.json +1 -1
  110. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  111. package/dist/cli/tui/fullscreen-renderer.js +0 -13
@@ -0,0 +1,257 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { parseTeamId, parseTeamSnapshot, } from '../core/team-ownership.js';
4
+ import { writeFileAtomically } from '../platform/atomic-write.js';
5
+ import { ExclusiveFileLease, } from '../platform/exclusive-file-lease.js';
6
+ import { resolveProjectIdentity } from '../platform/project-identity.js';
7
+ import { sanitizeProjectPath } from '../platform/project-path-key.js';
8
+ function assertTeamId(teamId) {
9
+ parseTeamId(teamId);
10
+ }
11
+ function assertRoot(root) {
12
+ if (typeof root !== 'string' || root.trim() === '')
13
+ throw new Error('Invalid native Team root');
14
+ return resolve(root);
15
+ }
16
+ export class NativeTeamStore {
17
+ nativeRoot;
18
+ projectIdentity;
19
+ projectRoot;
20
+ mutation = Promise.resolve();
21
+ constructor(nativeRoot, projectIdentity) {
22
+ this.nativeRoot = nativeRoot;
23
+ this.projectIdentity = projectIdentity;
24
+ this.projectRoot = resolve(nativeRoot, 'state', 'teams', sanitizeProjectPath(projectIdentity));
25
+ }
26
+ static async open(options) {
27
+ const root = assertRoot(options.nativeRoot);
28
+ const projectIdentity = await resolveProjectIdentity(options.cwd);
29
+ return new NativeTeamStore(root, projectIdentity);
30
+ }
31
+ async createAndClaim(snapshot) {
32
+ const parsed = parseTeamSnapshot(snapshot);
33
+ this.assertSnapshot(parsed, parsed.teamId);
34
+ if (parsed.revision !== 0)
35
+ throw new Error('Native Team creation requires revision 0');
36
+ const claim = await this.acquireClaim(parsed.teamId);
37
+ let mutation;
38
+ try {
39
+ mutation = await this.acquireMutation(parsed.teamId);
40
+ if (await this.exists(this.statePath(parsed.teamId)))
41
+ throw new Error(`Native Team state already exists: ${this.statePath(parsed.teamId)}`);
42
+ await writeFileAtomically(this.statePath(parsed.teamId), `${JSON.stringify(parsed)}\n`, { mode: 0o600 });
43
+ return claim;
44
+ }
45
+ catch (error) {
46
+ await claim.release();
47
+ throw error;
48
+ }
49
+ finally {
50
+ await mutation?.release();
51
+ }
52
+ }
53
+ async claim(teamId) {
54
+ assertTeamId(teamId);
55
+ const claim = await this.acquireClaim(teamId);
56
+ try {
57
+ const snapshot = await this.read(teamId);
58
+ if (snapshot === null)
59
+ throw new Error(`Missing native Team state: ${this.statePath(teamId)}`);
60
+ return claim;
61
+ }
62
+ catch (error) {
63
+ await claim.release();
64
+ throw error;
65
+ }
66
+ }
67
+ async read(teamId) {
68
+ assertTeamId(teamId);
69
+ const path = this.statePath(teamId);
70
+ let source;
71
+ try {
72
+ source = await readFile(path, 'utf8');
73
+ }
74
+ catch (error) {
75
+ if (error.code === 'ENOENT')
76
+ return null;
77
+ throw error;
78
+ }
79
+ let value;
80
+ try {
81
+ value = JSON.parse(source);
82
+ }
83
+ catch (error) {
84
+ throw new Error(`Corrupt native Team state: ${path}`, { cause: error });
85
+ }
86
+ let snapshot;
87
+ try {
88
+ snapshot = parseTeamSnapshot(value);
89
+ this.assertSnapshot(snapshot, teamId);
90
+ }
91
+ catch (error) {
92
+ if (error instanceof Error &&
93
+ error.message.startsWith('Invalid native Team state:'))
94
+ throw error;
95
+ throw new Error(`Invalid native Team state: ${path}`, { cause: error });
96
+ }
97
+ return snapshot;
98
+ }
99
+ async list() {
100
+ let entries;
101
+ try {
102
+ entries = await readdir(this.projectRoot, { withFileTypes: true });
103
+ }
104
+ catch (error) {
105
+ if (error.code === 'ENOENT')
106
+ return [];
107
+ throw error;
108
+ }
109
+ const ids = entries
110
+ .filter((entry) => {
111
+ if (!entry.isDirectory())
112
+ return false;
113
+ try {
114
+ parseTeamId(entry.name);
115
+ return true;
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ })
121
+ .map((entry) => entry.name)
122
+ .sort();
123
+ const snapshots = [];
124
+ for (const id of ids) {
125
+ const snapshot = await this.read(id);
126
+ if (snapshot === null)
127
+ throw new Error(`Invalid native Team state: ${this.statePath(id)}`);
128
+ snapshots.push(snapshot);
129
+ }
130
+ return Object.freeze(snapshots);
131
+ }
132
+ async acquireClaim(teamId) {
133
+ assertTeamId(teamId);
134
+ const lease = await new ExclusiveFileLease(`${this.statePath(teamId)}.owner.lock`).tryAcquire();
135
+ if (!lease)
136
+ throw new Error(`Team ${teamId} is already owned`);
137
+ return new NativeTeamClaimImpl(teamId, lease, async () => {
138
+ const snapshot = await this.read(teamId);
139
+ if (snapshot === null)
140
+ throw new Error(`Missing native Team state: ${this.statePath(teamId)}`);
141
+ return snapshot;
142
+ }, (expectedRevision, next) => this.save(teamId, expectedRevision, next));
143
+ }
144
+ async acquireMutation(teamId) {
145
+ const leaseFile = `${this.statePath(teamId)}.mutation.lock`;
146
+ const deadline = Date.now() + 5_000;
147
+ for (;;) {
148
+ const lease = await new ExclusiveFileLease(leaseFile).tryAcquire();
149
+ if (lease)
150
+ return lease;
151
+ if (Date.now() >= deadline)
152
+ throw new Error(`Team ${teamId} mutation is busy: ${this.statePath(teamId)}`);
153
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 10));
154
+ }
155
+ }
156
+ enqueue(operation) {
157
+ const queued = this.mutation.then(operation);
158
+ this.mutation = queued.then(() => undefined, () => undefined);
159
+ return queued;
160
+ }
161
+ async save(teamId, expectedRevision, next) {
162
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
163
+ throw new Error('Invalid expected Team revision');
164
+ return this.enqueue(async () => {
165
+ const mutation = await this.acquireMutation(teamId);
166
+ try {
167
+ const current = await this.read(teamId);
168
+ if (current === null)
169
+ throw new Error(`Missing native Team state: ${this.statePath(teamId)}`);
170
+ if (current.revision !== expectedRevision)
171
+ throw new Error(`Team revision conflict: expected ${expectedRevision}, found ${current.revision}`);
172
+ const parsed = parseTeamSnapshot(next);
173
+ this.assertSnapshot(parsed, teamId);
174
+ if (parsed.teamId !== current.teamId ||
175
+ parsed.projectIdentity !== current.projectIdentity ||
176
+ parsed.leadSessionId !== current.leadSessionId ||
177
+ parsed.createdAt !== current.createdAt)
178
+ throw new Error('Immutable native Team identity fields changed');
179
+ if (JSON.stringify(parsed.policy) !== JSON.stringify(current.policy) ||
180
+ JSON.stringify(parsed.budgets) !== JSON.stringify(current.budgets))
181
+ throw new Error('Immutable native Team policy or budgets changed');
182
+ if (parsed.revision !== current.revision + 1 ||
183
+ parsed.updatedAt < current.updatedAt)
184
+ throw new Error('Invalid native Team revision or timestamp');
185
+ await writeFileAtomically(this.statePath(teamId), `${JSON.stringify(parsed)}\n`, { mode: 0o600 });
186
+ return parsed;
187
+ }
188
+ finally {
189
+ await mutation.release();
190
+ }
191
+ });
192
+ }
193
+ assertSnapshot(snapshot, teamId) {
194
+ if (snapshot.projectIdentity !== this.projectIdentity ||
195
+ snapshot.teamId !== teamId)
196
+ throw new Error(`Invalid native Team state: ${this.statePath(teamId)}`);
197
+ }
198
+ statePath(teamId) {
199
+ return resolve(this.projectRoot, teamId, 'team.json');
200
+ }
201
+ async exists(path) {
202
+ try {
203
+ await readFile(path);
204
+ return true;
205
+ }
206
+ catch (error) {
207
+ if (error.code === 'ENOENT')
208
+ return false;
209
+ throw error;
210
+ }
211
+ }
212
+ }
213
+ class NativeTeamClaimImpl {
214
+ lease;
215
+ readState;
216
+ saveState;
217
+ active = true;
218
+ operations = Promise.resolve();
219
+ releasePromise;
220
+ teamId;
221
+ token;
222
+ pid;
223
+ acquiredAt;
224
+ constructor(teamId, lease, readState, saveState) {
225
+ this.lease = lease;
226
+ this.readState = readState;
227
+ this.saveState = saveState;
228
+ this.teamId = teamId;
229
+ this.token = lease.token;
230
+ this.pid = lease.pid;
231
+ this.acquiredAt = lease.createdAt;
232
+ }
233
+ assertActive() {
234
+ if (!this.active)
235
+ throw new Error(`Team ${this.teamId} claim is released`);
236
+ }
237
+ read() {
238
+ return this.admit(() => this.readState());
239
+ }
240
+ save(expectedRevision, next) {
241
+ return this.admit(() => this.saveState(expectedRevision, next));
242
+ }
243
+ async release() {
244
+ if (this.releasePromise)
245
+ return this.releasePromise;
246
+ this.active = false;
247
+ this.releasePromise = this.operations.then(() => this.lease.release());
248
+ return this.releasePromise;
249
+ }
250
+ admit(operation) {
251
+ this.assertActive();
252
+ const admitted = this.operations.then(operation, operation);
253
+ this.operations = admitted.then(() => undefined, () => undefined);
254
+ return admitted;
255
+ }
256
+ }
257
+ //# sourceMappingURL=native-team-store.js.map
@@ -0,0 +1,53 @@
1
+ import { type DecodedTranscriptRecord, type TranscriptCodecDiagnostic } from '../core/transcript-codec.js';
2
+ export declare const NATIVE_TRANSCRIPT_INDEX_HEAD_BYTES: number;
3
+ export declare const NATIVE_TRANSCRIPT_INDEX_TAIL_BYTES: number;
4
+ export declare const NATIVE_TRANSCRIPT_INDEX_CONCURRENCY = 32;
5
+ export interface TranscriptFileFacts {
6
+ byteLength: number;
7
+ newlineTerminated: boolean;
8
+ updatedAt: string;
9
+ }
10
+ export interface NativeTranscriptFullRead extends TranscriptFileFacts {
11
+ format: 'native';
12
+ codecId: string;
13
+ codecVersion: string | number;
14
+ records: readonly DecodedTranscriptRecord[];
15
+ issue: TranscriptCodecDiagnostic | null;
16
+ validPrefixByteLength: number;
17
+ writeMode: 'read-only';
18
+ raw: Buffer;
19
+ }
20
+ export interface LegacyTranscriptFullRead extends TranscriptFileFacts {
21
+ format: 'legacy';
22
+ raw: Buffer;
23
+ }
24
+ export type TranscriptReadResult = NativeTranscriptFullRead | LegacyTranscriptFullRead;
25
+ export interface NativeTranscriptIndex extends TranscriptFileFacts {
26
+ format: 'native';
27
+ codecId: string;
28
+ codecVersion: string | number;
29
+ records: readonly DecodedTranscriptRecord[];
30
+ issue: TranscriptCodecDiagnostic | null;
31
+ }
32
+ export interface LegacyTranscriptIndex extends TranscriptFileFacts {
33
+ format: 'legacy';
34
+ }
35
+ export type TranscriptIndexResult = NativeTranscriptIndex | LegacyTranscriptIndex;
36
+ export interface NativeTranscriptIndexRequest {
37
+ sessionId: string;
38
+ path: string;
39
+ }
40
+ export type NativeTranscriptIndexResponse = NativeTranscriptIndexRequest & ({
41
+ result: TranscriptIndexResult;
42
+ error?: never;
43
+ } | {
44
+ result?: never;
45
+ error: unknown;
46
+ });
47
+ export declare class NativeTranscriptIndexCandidateError extends Error {
48
+ readonly name = "NativeTranscriptIndexCandidateError";
49
+ }
50
+ export declare function readNativeTranscript(path: string): Promise<TranscriptReadResult>;
51
+ export declare function readNativeTranscriptIndexes(requests: readonly NativeTranscriptIndexRequest[], concurrency?: number): Promise<NativeTranscriptIndexResponse[]>;
52
+ export declare function exportNativeTranscript(path: string): Promise<Buffer>;
53
+ //# sourceMappingURL=native-transcript-reader.d.ts.map
@@ -0,0 +1,283 @@
1
+ import { lstat, open, readFile } from 'node:fs/promises';
2
+ import { TextDecoder } from 'node:util';
3
+ import { diagnostic, } from '../core/transcript-codec.js';
4
+ import { createNativeTranscriptCodec } from './native-transcript-codec.js';
5
+ export const NATIVE_TRANSCRIPT_INDEX_HEAD_BYTES = 64 * 1024;
6
+ export const NATIVE_TRANSCRIPT_INDEX_TAIL_BYTES = 128 * 1024;
7
+ export const NATIVE_TRANSCRIPT_INDEX_CONCURRENCY = 32;
8
+ const MAX_PROBE_LINE_BYTES = 1024 * 1024;
9
+ const CANONICAL_DECLARATION_PREFIX = Buffer.from('{"schema":"praxis.transcript","version":');
10
+ const CANONICAL_EVENT_SEPARATOR = Buffer.from(',"event":');
11
+ const MAX_CANONICAL_DECLARATION_BYTES = 256;
12
+ const UTF8 = new TextDecoder('utf-8', { fatal: true });
13
+ export class NativeTranscriptIndexCandidateError extends Error {
14
+ name = 'NativeTranscriptIndexCandidateError';
15
+ }
16
+ function facts(size, mtime, newlineTerminated) {
17
+ return { byteLength: size, updatedAt: mtime.toISOString(), newlineTerminated };
18
+ }
19
+ function declaration(line) {
20
+ try {
21
+ const value = JSON.parse(line);
22
+ if (!value || value.schema !== 'praxis.transcript')
23
+ return { declared: false };
24
+ return {
25
+ declared: true,
26
+ version: typeof value.version === 'string' || typeof value.version === 'number'
27
+ ? value.version
28
+ : 0,
29
+ };
30
+ }
31
+ catch {
32
+ return { declared: false };
33
+ }
34
+ }
35
+ function probe(source, options = {}) {
36
+ const maxLineBytes = options.maxLineBytes ?? MAX_PROBE_LINE_BYTES;
37
+ let start = 0;
38
+ while (start < source.length) {
39
+ let end = source.indexOf(0x0a, start);
40
+ if (end < 0) {
41
+ if (!options.allowUnterminated)
42
+ return null;
43
+ end = source.length;
44
+ }
45
+ if (end - start > maxLineBytes)
46
+ return null;
47
+ const line = source.subarray(start, end);
48
+ start = end + 1;
49
+ if (!line.length)
50
+ continue;
51
+ try {
52
+ return UTF8.decode(line);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ function canonicalPrefixDeclaration(source) {
61
+ if (source.length < CANONICAL_DECLARATION_PREFIX.length ||
62
+ !source
63
+ .subarray(0, CANONICAL_DECLARATION_PREFIX.length)
64
+ .equals(CANONICAL_DECLARATION_PREFIX))
65
+ return { declared: false };
66
+ const declarationEnd = source.indexOf(CANONICAL_EVENT_SEPARATOR, CANONICAL_DECLARATION_PREFIX.length);
67
+ if (declarationEnd < 0 || declarationEnd > MAX_CANONICAL_DECLARATION_BYTES)
68
+ return { declared: false };
69
+ try {
70
+ const version = JSON.parse(UTF8.decode(source.subarray(CANONICAL_DECLARATION_PREFIX.length, declarationEnd)));
71
+ return typeof version === 'string' || typeof version === 'number'
72
+ ? { declared: true, version }
73
+ : { declared: true, version: 0 };
74
+ }
75
+ catch {
76
+ return { declared: true, version: 0 };
77
+ }
78
+ }
79
+ function nativeResult(source, metadata) {
80
+ const line = probe(source, {
81
+ allowUnterminated: true,
82
+ maxLineBytes: Number.POSITIVE_INFINITY,
83
+ });
84
+ const parsed = line ? declaration(line) : { declared: false };
85
+ const info = parsed.declared ? parsed : canonicalPrefixDeclaration(source);
86
+ if (!info.declared)
87
+ return null;
88
+ const version = info.version ?? 0;
89
+ const codec = createNativeTranscriptCodec(typeof version === 'number' ? version : 0);
90
+ const decoded = codec.decodeDocument(source);
91
+ return {
92
+ ...facts(source.length, metadata.mtime, source.length === 0 || source.at(-1) === 0x0a),
93
+ format: 'native',
94
+ codecId: codec.id,
95
+ codecVersion: version,
96
+ records: decoded.records,
97
+ issue: decoded.issue,
98
+ validPrefixByteLength: decoded.validPrefixByteLength,
99
+ writeMode: 'read-only',
100
+ raw: source,
101
+ };
102
+ }
103
+ export async function readNativeTranscript(path) {
104
+ const metadata = await lstat(path);
105
+ if (!metadata.isFile())
106
+ throw new Error(`Expected a regular file: ${path}`);
107
+ const source = await readFile(path);
108
+ const native = nativeResult(source, metadata);
109
+ if (native)
110
+ return native;
111
+ return {
112
+ ...facts(source.length, metadata.mtime, source.length === 0 || source.at(-1) === 0x0a),
113
+ format: 'legacy',
114
+ raw: source,
115
+ };
116
+ }
117
+ function linesInWindow(source, absoluteStart, discardLeading, discardTrailing, lineNumber) {
118
+ let start = 0;
119
+ if (discardLeading) {
120
+ const newline = source.indexOf(0x0a);
121
+ if (newline < 0)
122
+ return [];
123
+ start = newline + 1;
124
+ }
125
+ let end = source.length;
126
+ if (discardTrailing && source.at(-1) !== 0x0a) {
127
+ const newline = source.lastIndexOf(0x0a);
128
+ if (newline < start)
129
+ return [];
130
+ end = newline + 1;
131
+ }
132
+ const result = [];
133
+ let lineStart = start;
134
+ for (let i = start; i < end; i++)
135
+ if (source[i] === 0x0a) {
136
+ result.push({
137
+ source: source.subarray(lineStart, i),
138
+ offset: absoluteStart + lineStart,
139
+ lineNumber: lineNumber === null ? null : lineNumber + result.length,
140
+ terminated: true,
141
+ });
142
+ lineStart = i + 1;
143
+ }
144
+ if (!discardTrailing && lineStart < end)
145
+ result.push({
146
+ source: source.subarray(lineStart, end),
147
+ offset: absoluteStart + lineStart,
148
+ lineNumber: lineNumber === null ? null : lineNumber + result.length,
149
+ terminated: false,
150
+ });
151
+ return result;
152
+ }
153
+ async function readWindow(handle, length, position) {
154
+ const buffer = Buffer.alloc(length);
155
+ let total = 0;
156
+ while (total < length) {
157
+ const { bytesRead } = await handle.read(buffer, total, length - total, position + total);
158
+ if (bytesRead === 0)
159
+ break;
160
+ total += bytesRead;
161
+ }
162
+ return buffer.subarray(0, total);
163
+ }
164
+ async function indexOne(path) {
165
+ const pathMetadata = await lstat(path);
166
+ if (!pathMetadata.isFile())
167
+ throw new NativeTranscriptIndexCandidateError(`Expected a regular file: ${path}`);
168
+ const handle = await open(path, 'r');
169
+ try {
170
+ const metadata = await handle.stat();
171
+ const size = metadata.size;
172
+ const total = NATIVE_TRANSCRIPT_INDEX_HEAD_BYTES + NATIVE_TRANSCRIPT_INDEX_TAIL_BYTES;
173
+ const wholeFile = size <= total;
174
+ const headLength = wholeFile ? size : NATIVE_TRANSCRIPT_INDEX_HEAD_BYTES;
175
+ const head = await readWindow(handle, headLength, 0);
176
+ const tailStart = wholeFile
177
+ ? head.length
178
+ : size - NATIVE_TRANSCRIPT_INDEX_TAIL_BYTES;
179
+ const tailLength = wholeFile ? 0 : NATIVE_TRANSCRIPT_INDEX_TAIL_BYTES;
180
+ const tail = await readWindow(handle, tailLength, tailStart);
181
+ const tailStartsMidLine = !wholeFile && (await readWindow(handle, 1, tailStart - 1)).at(0) !== 0x0a;
182
+ const newlineTerminated = size === 0 || (tailLength ? tail.at(-1) === 0x0a : head.at(-1) === 0x0a);
183
+ let first = probe(head, { allowUnterminated: wholeFile });
184
+ if (first === null && !wholeFile) {
185
+ const probeLength = Math.min(size, MAX_PROBE_LINE_BYTES + 1);
186
+ first = probe(await readWindow(handle, probeLength, 0));
187
+ }
188
+ const parsed = first ? declaration(first) : { declared: false };
189
+ const info = parsed.declared ? parsed : canonicalPrefixDeclaration(head);
190
+ const file = facts(size, metadata.mtime, newlineTerminated);
191
+ if (!info.declared)
192
+ return { ...file, format: 'legacy' };
193
+ const version = info.version ?? 0;
194
+ const codec = createNativeTranscriptCodec(typeof version === 'number' ? version : 0);
195
+ if (version !== 1)
196
+ return {
197
+ ...file,
198
+ format: 'native',
199
+ codecId: codec.id,
200
+ codecVersion: version,
201
+ records: [],
202
+ issue: diagnostic('unsupported-version', `Unsupported native transcript version ${String(version)}`, 0, 1, { schemaVersion: version }),
203
+ };
204
+ const records = [];
205
+ let issue = null;
206
+ const windows = !wholeFile
207
+ ? [
208
+ ...linesInWindow(head, 0, false, true, 1),
209
+ ...linesInWindow(tail, tailStart, tailStartsMidLine, false, null),
210
+ ]
211
+ : linesInWindow(head, 0, false, false, 1);
212
+ for (const line of windows) {
213
+ let text;
214
+ try {
215
+ text = UTF8.decode(line.source);
216
+ }
217
+ catch {
218
+ if (!line.terminated)
219
+ break;
220
+ issue = {
221
+ kind: 'corrupt-line',
222
+ message: 'Invalid UTF-8 transcript line',
223
+ byteOffset: line.offset,
224
+ lineNumber: line.lineNumber,
225
+ };
226
+ break;
227
+ }
228
+ const decoded = codec.decodeLine(text, line.lineNumber ?? undefined, line.offset);
229
+ if (!decoded.ok) {
230
+ if (!line.terminated)
231
+ try {
232
+ JSON.parse(text);
233
+ }
234
+ catch {
235
+ break;
236
+ }
237
+ issue = decoded.issue;
238
+ break;
239
+ }
240
+ records.push(decoded.record);
241
+ }
242
+ return {
243
+ ...file,
244
+ format: 'native',
245
+ codecId: codec.id,
246
+ codecVersion: version,
247
+ records,
248
+ issue,
249
+ };
250
+ }
251
+ finally {
252
+ await handle.close();
253
+ }
254
+ }
255
+ export async function readNativeTranscriptIndexes(requests, concurrency = NATIVE_TRANSCRIPT_INDEX_CONCURRENCY) {
256
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1)
257
+ throw new Error('Native transcript index concurrency must be a positive integer');
258
+ const result = new Array(requests.length);
259
+ let next = 0;
260
+ const worker = async () => {
261
+ while (next < requests.length) {
262
+ const index = next++;
263
+ const request = requests[index];
264
+ if (!request)
265
+ continue;
266
+ try {
267
+ result[index] = { ...request, result: await indexOne(request.path) };
268
+ }
269
+ catch (error) {
270
+ result[index] = { ...request, error };
271
+ }
272
+ }
273
+ };
274
+ await Promise.all(Array.from({ length: Math.min(concurrency, requests.length) }, worker));
275
+ return result;
276
+ }
277
+ export async function exportNativeTranscript(path) {
278
+ const metadata = await lstat(path);
279
+ if (!metadata.isFile())
280
+ throw new Error(`Expected a regular file: ${path}`);
281
+ return readFile(path);
282
+ }
283
+ //# sourceMappingURL=native-transcript-reader.js.map
@@ -0,0 +1,69 @@
1
+ import type { TranscriptEvent } from '../core/transcript-event.js';
2
+ import { type DecodedTranscriptRecord, type TranscriptCodecDiagnostic } from '../core/transcript-codec.js';
3
+ export interface NativeTranscriptTail {
4
+ byteLength: number;
5
+ lastLineHash: string | null;
6
+ lastEventId: string | null;
7
+ newlineTerminated: boolean;
8
+ branchParentId?: string | null;
9
+ }
10
+ export interface NativeTranscriptSnapshot {
11
+ records: readonly DecodedTranscriptRecord[];
12
+ tail: NativeTranscriptTail;
13
+ }
14
+ export interface NativeTranscriptRecovery extends NativeTranscriptSnapshot {
15
+ issue: TranscriptCodecDiagnostic | null;
16
+ }
17
+ export type NativeTranscriptAppendResult = {
18
+ status: 'appended';
19
+ tail: NativeTranscriptTail;
20
+ } | {
21
+ status: 'conflict';
22
+ reason: 'interleaved-write' | 'locked' | 'tail-changed';
23
+ };
24
+ export type NativeTranscriptCreateResult = {
25
+ status: 'created';
26
+ tail: NativeTranscriptTail;
27
+ } | {
28
+ status: 'conflict';
29
+ reason: 'already-exists';
30
+ };
31
+ export type NativeTranscriptReserveResult = {
32
+ status: 'reserved';
33
+ } | {
34
+ status: 'conflict';
35
+ reason: 'already-exists';
36
+ };
37
+ export interface NativeTranscriptLease {
38
+ reserve(): Promise<NativeTranscriptReserveResult>;
39
+ load(): Promise<NativeTranscriptSnapshot>;
40
+ append(expectedTail: NativeTranscriptTail, event: TranscriptEvent): Promise<NativeTranscriptAppendResult>;
41
+ appendMany(expectedTail: NativeTranscriptTail, events: readonly TranscriptEvent[]): Promise<NativeTranscriptAppendResult>;
42
+ }
43
+ export type NativeTranscriptLeaseResult<T> = {
44
+ status: 'completed';
45
+ value: T;
46
+ } | {
47
+ status: 'conflict';
48
+ reason: 'locked';
49
+ };
50
+ export declare class NativeTranscriptStore {
51
+ private readonly options;
52
+ private readonly lease;
53
+ constructor(options: {
54
+ transcriptFile: string;
55
+ lockFile: string;
56
+ });
57
+ private source;
58
+ private decode;
59
+ load(): Promise<NativeTranscriptSnapshot>;
60
+ loadReadOnly(): Promise<NativeTranscriptRecovery>;
61
+ exportReadOnly(): Promise<Buffer<ArrayBuffer>>;
62
+ private prepare;
63
+ create(events: readonly TranscriptEvent[]): Promise<NativeTranscriptCreateResult>;
64
+ private reserveUnderLease;
65
+ withLease<T>(operation: (lease: NativeTranscriptLease) => Promise<T>): Promise<NativeTranscriptLeaseResult<T>>;
66
+ private appendUnderLease;
67
+ append(expectedTail: NativeTranscriptTail, event: TranscriptEvent): Promise<NativeTranscriptAppendResult>;
68
+ }
69
+ //# sourceMappingURL=native-transcript-store.d.ts.map