praxis-agent 0.36.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 (86) hide show
  1. package/README.md +16 -2
  2. package/dist/application/durable-follow-up.d.ts +14 -0
  3. package/dist/application/durable-follow-up.js +52 -0
  4. package/dist/application/native-session-transcript.d.ts +72 -0
  5. package/dist/application/native-session-transcript.js +351 -0
  6. package/dist/application/native-sidechain-transcript.d.ts +34 -0
  7. package/dist/application/native-sidechain-transcript.js +156 -0
  8. package/dist/application/session-service.d.ts +15 -0
  9. package/dist/application/session-service.js +611 -69
  10. package/dist/application/subagent-service.d.ts +5 -1
  11. package/dist/application/subagent-service.js +391 -143
  12. package/dist/application/team-agent-runtime.d.ts +29 -0
  13. package/dist/application/team-agent-runtime.js +169 -0
  14. package/dist/application/team-capability.d.ts +27 -0
  15. package/dist/application/team-capability.js +41 -0
  16. package/dist/application/team-lead-operations.d.ts +38 -0
  17. package/dist/application/team-lead-operations.js +155 -0
  18. package/dist/application/team-mailbox.d.ts +54 -0
  19. package/dist/application/team-mailbox.js +196 -0
  20. package/dist/application/team-manager.d.ts +113 -0
  21. package/dist/application/team-manager.js +681 -0
  22. package/dist/application/team-workspace.d.ts +29 -0
  23. package/dist/application/team-workspace.js +197 -0
  24. package/dist/application/transcript-projection.d.ts +2 -0
  25. package/dist/application/transcript-projection.js +30 -1
  26. package/dist/cli/interactive.d.ts +7 -0
  27. package/dist/cli/interactive.js +387 -136
  28. package/dist/cli/tui/claude-style.d.ts +8 -25
  29. package/dist/cli/tui/claude-style.js +158 -160
  30. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  31. package/dist/cli/tui/decision-surface-model.js +131 -0
  32. package/dist/cli/tui/decision-surface.d.ts +7 -0
  33. package/dist/cli/tui/decision-surface.js +41 -0
  34. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  35. package/dist/cli/tui/help-surface-model.js +131 -0
  36. package/dist/cli/tui/mcp-panel.js +1 -2
  37. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  38. package/dist/cli/tui/permission-surface-model.js +360 -0
  39. package/dist/cli/tui/permission-surface.d.ts +7 -0
  40. package/dist/cli/tui/permission-surface.js +69 -0
  41. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  42. package/dist/cli/tui/presentation-environment.js +149 -0
  43. package/dist/cli/tui/tool-permission.js +7 -3
  44. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  45. package/dist/cli/tui/transcript-presentation.js +26 -6
  46. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  47. package/dist/cli/tui/transcript-viewport.js +517 -111
  48. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  49. package/dist/cli/tui/transcript-window-model.js +772 -0
  50. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  51. package/dist/cli/tui/tui-screen-model.js +210 -0
  52. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  53. package/dist/cli/tui/tui-view-model.js +1 -68
  54. package/dist/cli-runtime.d.ts +2 -0
  55. package/dist/cli-runtime.js +397 -32
  56. package/dist/compatibility/claude/transcript-codec.js +6 -0
  57. package/dist/core/permission-policy.d.ts +4 -0
  58. package/dist/core/permission-policy.js +21 -0
  59. package/dist/core/runtime.d.ts +1 -0
  60. package/dist/core/runtime.js +5 -1
  61. package/dist/core/team-mailbox.d.ts +54 -0
  62. package/dist/core/team-mailbox.js +212 -0
  63. package/dist/core/team-ownership.d.ts +84 -0
  64. package/dist/core/team-ownership.js +505 -0
  65. package/dist/core/tool-execution-scheduler.js +6 -1
  66. package/dist/core/transcript-event.d.ts +5 -1
  67. package/dist/core/transcript-event.js +12 -0
  68. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  69. package/dist/persistence/claude-transcript-store.js +2 -10
  70. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  71. package/dist/persistence/native-team-mailbox.js +542 -0
  72. package/dist/persistence/native-team-store.d.ts +34 -0
  73. package/dist/persistence/native-team-store.js +257 -0
  74. package/dist/persistence/native-transcript-store.d.ts +69 -0
  75. package/dist/persistence/native-transcript-store.js +321 -0
  76. package/dist/persistence/transcript-file-append.d.ts +2 -0
  77. package/dist/persistence/transcript-file-append.js +10 -0
  78. package/dist/tools/claude-capabilities.d.ts +4 -0
  79. package/dist/tools/claude-capabilities.js +18 -2
  80. package/dist/tools/team-lead-tools.d.ts +19 -0
  81. package/dist/tools/team-lead-tools.js +479 -0
  82. package/dist/tools/team-member-tools.d.ts +21 -0
  83. package/dist/tools/team-member-tools.js +259 -0
  84. package/package.json +1 -1
  85. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  86. package/dist/cli/tui/fullscreen-renderer.js +0 -13
package/README.md CHANGED
@@ -122,7 +122,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
122
122
  for supported local terminals,
123
123
  `Ctrl+V` text/image clipboard paste, `Ctrl+Z` shell suspension and `fg`
124
124
  recovery, permission-gated `!` shell turns, navigable current/per-turn Git
125
- diff views, rich decision panels, and measured context budgets; print mode,
125
+ diff views, semantic plan/question decision panels with complete
126
+ screen-reader actions, and measured context budgets; print mode,
126
127
  structured JSON/JSONL, context compaction, tool loops, and bounded execution.
127
128
  - **Built-in tools** — read, write, edit, glob, search, shell, notebook, PDF,
128
129
  image, web, scheduled prompts, workflows, and worktrees.
@@ -152,7 +153,20 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
152
153
  Claude-compatible main-thread agent definitions with native prompt, model,
153
154
  tool, memory, first-turn, and resume behavior. Agent execution uses one
154
155
  durable lifecycle vocabulary with bounded cancellation and drain,
155
- continuation, notifications, and single-owner orphan recovery.
156
+ continuation, notifications, and single-owner orphan recovery. Experimental
157
+ local Teams (`PRAXIS_ENABLE_TEAMS=true`) stay absent from ordinary startup by
158
+ default and add durable task ownership plus one ordered mailbox with stable
159
+ identities, fixed broadcast recipients, durable cursors, bounded retention,
160
+ and bounded model-context projection. Teams are explicitly experimental and
161
+ opt-in: `PRAXIS_ENABLE_TEAMS=true` is required; otherwise Team code is not
162
+ loaded, discovered, or exposed. New Teams default to Hybrid lead control and
163
+ sequential execution, with optional Coordinator and Swarm policies. Commits
164
+ remain Lead-owned. Swarm admits only independent, dependency-ready,
165
+ non-conflicting tasks, within durable agent, concurrency, token, duration,
166
+ and shutdown-drain budgets.
167
+ Child permissions can only tighten the parent; concurrent asks form one FIFO
168
+ Lead Decision queue with provenance. Coordinator leads are restricted to
169
+ orchestration, and custom Team agents receive no MCP capability.
156
170
  - **Claude-compatible ecosystem** — shared instructions with recursive `@`
157
171
  imports, memory, skills, commands, agents, hooks, settings, MCP servers,
158
172
  plugins, and transcript data.
@@ -0,0 +1,14 @@
1
+ export interface DurableFollowUpBatch {
2
+ readonly id: string;
3
+ readonly messages: readonly string[];
4
+ acknowledge(): Promise<void>;
5
+ }
6
+ export declare class DurableFollowUpTracker {
7
+ private readonly pending;
8
+ register(batch: DurableFollowUpBatch): void;
9
+ add(batch: DurableFollowUpBatch): void;
10
+ track(batch: DurableFollowUpBatch): void;
11
+ followUpUserMessagesCompleted(messages: readonly string[]): Promise<void>;
12
+ size(): number;
13
+ }
14
+ //# sourceMappingURL=durable-follow-up.d.ts.map
@@ -0,0 +1,52 @@
1
+ function multiset(values) {
2
+ const result = new Map();
3
+ for (const value of values)
4
+ result.set(value, (result.get(value) ?? 0) + 1);
5
+ return result;
6
+ }
7
+ function containsAll(container, required) {
8
+ const available = multiset(container);
9
+ for (const [value, count] of multiset(required)) {
10
+ if ((available.get(value) ?? 0) < count)
11
+ return false;
12
+ }
13
+ return true;
14
+ }
15
+ export class DurableFollowUpTracker {
16
+ pending = new Map();
17
+ register(batch) {
18
+ if (!batch || typeof batch.id !== 'string' || batch.id.trim() === '')
19
+ throw new Error('Invalid durable follow-up batch');
20
+ if (!Array.isArray(batch.messages) || batch.messages.length === 0)
21
+ return;
22
+ if (batch.messages.some((message) => typeof message !== 'string'))
23
+ throw new Error('Invalid durable follow-up messages');
24
+ const existing = this.pending.get(batch.id);
25
+ if (existing) {
26
+ if (existing.messages.length !== batch.messages.length ||
27
+ existing.messages.some((message, index) => message !== batch.messages[index]))
28
+ throw new Error(`Conflicting durable follow-up batch: ${batch.id}`);
29
+ return;
30
+ }
31
+ this.pending.set(batch.id, batch);
32
+ }
33
+ add(batch) {
34
+ this.register(batch);
35
+ }
36
+ track(batch) {
37
+ this.register(batch);
38
+ }
39
+ async followUpUserMessagesCompleted(messages) {
40
+ const completed = [...messages];
41
+ for (const [id, batch] of this.pending) {
42
+ if (!containsAll(completed, batch.messages))
43
+ continue;
44
+ await batch.acknowledge();
45
+ this.pending.delete(id);
46
+ }
47
+ }
48
+ size() {
49
+ return this.pending.size;
50
+ }
51
+ }
52
+ //# sourceMappingURL=durable-follow-up.js.map
@@ -0,0 +1,72 @@
1
+ import type { ModelMessage, ModelToolCall, ToolExecutionResult } from '../core/runtime.js';
2
+ import type { TranscriptMessagesEvent } from '../core/transcript-event.js';
3
+ import type { NativeTranscriptStore } from '../persistence/native-transcript-store.js';
4
+ export type NativeTranscriptActivation = {
5
+ readonly kind: 'start';
6
+ } | {
7
+ readonly kind: 'resume';
8
+ readonly atEventId?: string;
9
+ };
10
+ export interface NativeMessageAppend {
11
+ readonly messages: readonly ModelMessage[];
12
+ readonly model?: string;
13
+ readonly terminalReason?: TranscriptMessagesEvent['terminalReason'];
14
+ }
15
+ export interface NativeCompactionAppend {
16
+ readonly summary: string;
17
+ readonly trigger: 'auto' | 'manual';
18
+ readonly preTokens: number;
19
+ readonly postTokens: number;
20
+ readonly durationMs: number;
21
+ readonly preservedMessages?: readonly ModelMessage[];
22
+ }
23
+ export type NativeInterruption = {
24
+ readonly kind: 'complete' | 'none';
25
+ } | {
26
+ readonly kind: 'interrupted-prompt';
27
+ readonly prompt: string;
28
+ } | {
29
+ readonly kind: 'interrupted-turn';
30
+ } | {
31
+ readonly kind: 'recoverable-tools';
32
+ readonly calls: readonly ModelToolCall[];
33
+ } | {
34
+ readonly kind: 'indeterminate-tools';
35
+ readonly callIds: readonly string[];
36
+ };
37
+ export interface NativeSessionTranscriptLease {
38
+ activeMessages(): ModelMessage[];
39
+ interruption(): NativeInterruption;
40
+ beginToolExecution(callId: string): Promise<void>;
41
+ appendToolCompletion(input: {
42
+ callId: string;
43
+ result: ToolExecutionResult;
44
+ followUpUserMessages?: readonly string[];
45
+ }): Promise<void>;
46
+ appendMessages(input: NativeMessageAppend): Promise<string>;
47
+ appendCompaction(input: NativeCompactionAppend): Promise<{
48
+ boundaryId: string;
49
+ summaryId: string;
50
+ }>;
51
+ }
52
+ export interface NativeForkOptions {
53
+ readonly atEventId?: string;
54
+ readonly recordCount?: number;
55
+ readonly ensureExisting?: boolean;
56
+ }
57
+ export interface NativeSessionTranscriptOptions {
58
+ readonly sessionId: string;
59
+ readonly store: NativeTranscriptStore;
60
+ readonly createId?: () => string;
61
+ readonly now?: () => string;
62
+ }
63
+ export declare class NativeSessionTranscript {
64
+ private readonly sessionId;
65
+ private readonly store;
66
+ private readonly createId;
67
+ private readonly now;
68
+ constructor(options: NativeSessionTranscriptOptions);
69
+ forkTo(target: NativeSessionTranscript, options?: NativeForkOptions): Promise<void>;
70
+ withLease<T>(activation: NativeTranscriptActivation, operation: (lease: NativeSessionTranscriptLease) => Promise<T>): Promise<T>;
71
+ }
72
+ //# sourceMappingURL=native-session-transcript.d.ts.map
@@ -0,0 +1,351 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { activeEvents, projectActiveMessages, unresolvedActiveToolCallIds, } from './transcript-projection.js';
3
+ export class NativeSessionTranscript {
4
+ sessionId;
5
+ store;
6
+ createId;
7
+ now;
8
+ constructor(options) {
9
+ this.sessionId = options.sessionId;
10
+ this.store = options.store;
11
+ this.createId = options.createId ?? randomUUID;
12
+ this.now = options.now ?? (() => new Date().toISOString());
13
+ }
14
+ async forkTo(target, options = {}) {
15
+ if (this.sessionId === target.sessionId)
16
+ throw new Error('native fork source and target session IDs must differ');
17
+ const sourceResult = await this.store.withLease((lease) => lease.load());
18
+ if (sourceResult.status === 'conflict')
19
+ throw new Error(`native fork source lease conflict: ${sourceResult.reason}`);
20
+ const source = sourceResult.value;
21
+ if (source.records.length === 0)
22
+ throw new Error('native fork source session is missing or empty');
23
+ if (source.records.some((record) => record.event.sessionId !== this.sessionId))
24
+ throw new Error('native fork source sessionId does not match');
25
+ const recordCount = options.recordCount;
26
+ if (recordCount !== undefined &&
27
+ (!Number.isSafeInteger(recordCount) ||
28
+ recordCount <= 0 ||
29
+ recordCount > source.records.length))
30
+ throw new Error('native fork source recordCount is invalid');
31
+ const prefix = source.records.slice(0, recordCount);
32
+ const selected = activeEvents(prefix.map((record) => record.event), options.atEventId);
33
+ const terminal = prefix.at(-1)?.event;
34
+ if (terminal?.kind === 'context-boundary' ||
35
+ selected.at(-1)?.kind === 'context-boundary')
36
+ throw new Error('native fork source ends with an incomplete context boundary');
37
+ const expected = selected.map((event) => ({
38
+ ...event,
39
+ sessionId: target.sessionId,
40
+ }));
41
+ const ensureExisting = options.ensureExisting === true;
42
+ const targetResult = await target.store.withLease(async (lease) => {
43
+ const reservation = await lease.reserve();
44
+ if (reservation.status === 'reserved') {
45
+ const empty = await lease.load();
46
+ const appended = await lease.appendMany(empty.tail, expected);
47
+ if (appended.status === 'conflict')
48
+ throw new Error(`native fork target append conflict: ${appended.reason}`);
49
+ return;
50
+ }
51
+ if (!ensureExisting)
52
+ throw new Error('native fork target already exists');
53
+ const existing = await lease.load();
54
+ if (existing.records.length < expected.length ||
55
+ expected.some((event, index) => JSON.stringify(existing.records[index]?.event) !==
56
+ JSON.stringify(event)))
57
+ throw new Error('native fork target is not the expected native fork');
58
+ });
59
+ if (targetResult.status === 'conflict')
60
+ throw new Error(`native fork target lease conflict: ${targetResult.reason}`);
61
+ }
62
+ async withLease(activation, operation) {
63
+ let prepared;
64
+ let selectedId;
65
+ const result = await this.store.withLease(async (nativeLease) => {
66
+ if (activation.kind === 'start') {
67
+ const reserved = await nativeLease.reserve();
68
+ if (reserved.status === 'conflict')
69
+ throw new Error('native transcript session already exists');
70
+ }
71
+ prepared = await nativeLease.load();
72
+ if (activation.kind === 'resume') {
73
+ if (prepared.records.length === 0)
74
+ throw new Error('native transcript session is missing or empty');
75
+ if (prepared.records.some((record) => record.event.sessionId !== this.sessionId))
76
+ throw new Error('native transcript sessionId does not match');
77
+ const events = prepared.records.map((record) => record.event);
78
+ const selected = activeEvents(events, activation.atEventId);
79
+ if (selected.length === 0)
80
+ throw new Error('native transcript session has no active events');
81
+ selectedId = selected.at(-1)?.id;
82
+ }
83
+ const initial = prepared;
84
+ const currentId = selectedId ?? initial.records.at(-1)?.event.id ?? null;
85
+ const records = [...initial.records];
86
+ let tail = currentId === null
87
+ ? initial.tail
88
+ : { ...initial.tail, branchParentId: currentId };
89
+ let activeId = currentId;
90
+ let mutationQueue = Promise.resolve();
91
+ const enqueue = (operation) => {
92
+ const result = mutationQueue.then(operation, operation);
93
+ mutationQueue = result.then(() => undefined, () => undefined);
94
+ return result;
95
+ };
96
+ const lease = {
97
+ activeMessages: () => projectActiveMessages(records.map((record) => record.event), activeId ?? undefined).slice(),
98
+ interruption: () => {
99
+ const events = activeEvents(records.map((record) => record.event), activeId ?? undefined);
100
+ const boundaryIndex = Math.max(-1, ...events
101
+ .map((event, index) => event.kind === 'context-boundary' ? index : -1)
102
+ .filter((index) => index >= 0));
103
+ const active = events.slice(boundaryIndex + 1);
104
+ const calls = new Map();
105
+ const results = new Set();
106
+ const claimed = new Set();
107
+ for (const event of active) {
108
+ if (event.kind === 'tool-execution-started') {
109
+ claimed.add(event.callId);
110
+ continue;
111
+ }
112
+ if (event.kind !== 'messages')
113
+ continue;
114
+ for (const message of event.messages) {
115
+ if (message.role === 'assistant')
116
+ for (const call of message.toolCalls ?? [])
117
+ calls.set(call.id, call);
118
+ else if (message.role === 'tool')
119
+ results.add(message.toolCallId);
120
+ }
121
+ }
122
+ const unresolved = [...calls.entries()].filter(([id]) => !results.has(id));
123
+ const indeterminate = unresolved
124
+ .map(([id]) => id)
125
+ .filter((id) => claimed.has(id));
126
+ if (indeterminate.length > 0)
127
+ return { kind: 'indeterminate-tools', callIds: indeterminate };
128
+ if (unresolved.length > 0)
129
+ return {
130
+ kind: 'recoverable-tools',
131
+ calls: unresolved.map(([, call]) => call),
132
+ };
133
+ let lastMessages;
134
+ for (const event of active) {
135
+ if (event.kind !== 'messages')
136
+ continue;
137
+ lastMessages = event.messages;
138
+ }
139
+ const lastMessage = lastMessages?.at(-1);
140
+ if (lastMessage?.role === 'assistant')
141
+ return { kind: 'complete' };
142
+ if (lastMessages?.some((message) => message.role === 'tool'))
143
+ return { kind: 'interrupted-turn' };
144
+ if (lastMessage?.role === 'user')
145
+ return { kind: 'interrupted-prompt', prompt: lastMessage.content };
146
+ return { kind: 'none' };
147
+ },
148
+ beginToolExecution: (callId) => enqueue(async () => {
149
+ const events = activeEvents(records.map((record) => record.event), activeId ?? undefined);
150
+ const activeMessages = projectActiveMessages(records.map((record) => record.event), activeId ?? undefined);
151
+ const call = activeMessages
152
+ .filter((message) => message.role === 'assistant')
153
+ .flatMap((message) => message.toolCalls ?? [])
154
+ .find((candidate) => candidate.id === callId);
155
+ if (!call)
156
+ throw new Error(`Unknown native tool call: ${callId}`);
157
+ const boundaryIndex = Math.max(-1, ...events.map((event, index) => event.kind === 'context-boundary' ? index : -1));
158
+ const postBoundary = events.slice(boundaryIndex + 1);
159
+ const resolved = new Set(projectActiveMessages(records.map((record) => record.event), activeId ?? undefined)
160
+ .filter((message) => message.role === 'tool')
161
+ .map((message) => message.toolCallId));
162
+ if (resolved.has(callId))
163
+ throw new Error(`Native tool call is already resolved: ${callId}`);
164
+ if (postBoundary.some((event) => event.kind === 'tool-execution-started' &&
165
+ event.callId === callId))
166
+ throw new Error(`Native tool call already claimed: ${callId}`);
167
+ const event = {
168
+ kind: 'tool-execution-started',
169
+ id: this.createId(),
170
+ parentId: activeId,
171
+ sessionId: this.sessionId,
172
+ timestamp: this.now(),
173
+ callId,
174
+ };
175
+ const appended = await nativeLease.append(tail, event);
176
+ if (appended.status === 'conflict')
177
+ throw new Error(`native transcript append conflict: ${appended.reason}`);
178
+ records.push({ event });
179
+ tail = appended.tail;
180
+ activeId = event.id;
181
+ }),
182
+ appendToolCompletion: (input) => enqueue(async () => {
183
+ const { callId, result, followUpUserMessages = [] } = input;
184
+ const events = activeEvents(records.map((record) => record.event), activeId ?? undefined);
185
+ const messages = projectActiveMessages(records.map((record) => record.event), activeId ?? undefined);
186
+ const call = messages
187
+ .filter((message) => message.role === 'assistant')
188
+ .flatMap((message) => message.toolCalls ?? [])
189
+ .find((candidate) => candidate.id === callId);
190
+ if (!call)
191
+ throw new Error(`Unknown native tool call: ${callId}`);
192
+ if (!events.some((event) => event.kind === 'tool-execution-started' &&
193
+ event.callId === callId))
194
+ throw new Error(`Native tool call was not claimed: ${callId}`);
195
+ if (messages.some((message) => message.role === 'tool' && message.toolCallId === callId))
196
+ throw new Error(`Native tool call is already resolved: ${callId}`);
197
+ const event = {
198
+ kind: 'messages',
199
+ id: this.createId(),
200
+ parentId: activeId,
201
+ sessionId: this.sessionId,
202
+ timestamp: this.now(),
203
+ messages: [
204
+ {
205
+ role: 'tool',
206
+ toolCallId: callId,
207
+ content: result.content,
208
+ ...(result.contentBlocks?.length
209
+ ? { contentBlocks: result.contentBlocks }
210
+ : {}),
211
+ ...(result.images?.length ? { images: result.images } : {}),
212
+ ...(result.documents?.length
213
+ ? { documents: result.documents }
214
+ : {}),
215
+ isError: result.isError,
216
+ },
217
+ ...followUpUserMessages.map((content) => ({
218
+ role: 'user',
219
+ content,
220
+ })),
221
+ ],
222
+ };
223
+ const appended = await nativeLease.append(tail, event);
224
+ if (appended.status === 'conflict')
225
+ throw new Error(`native transcript append conflict: ${appended.reason}`);
226
+ records.push({ event });
227
+ tail = appended.tail;
228
+ activeId = event.id;
229
+ }),
230
+ appendMessages: (input) => enqueue(async () => {
231
+ if (input.messages.length === 0)
232
+ throw new Error('native transcript cannot append empty messages');
233
+ const event = {
234
+ kind: 'messages',
235
+ id: this.createId(),
236
+ parentId: activeId,
237
+ sessionId: this.sessionId,
238
+ timestamp: this.now(),
239
+ messages: [...input.messages],
240
+ ...(input.model === undefined ? {} : { model: input.model }),
241
+ ...(input.terminalReason === undefined
242
+ ? {}
243
+ : { terminalReason: input.terminalReason }),
244
+ };
245
+ const appended = await nativeLease.append(tail, event);
246
+ if (appended.status === 'conflict')
247
+ throw new Error(`native transcript append conflict: ${appended.reason}`);
248
+ records.push({ event });
249
+ tail = appended.tail;
250
+ activeId = event.id;
251
+ return event.id;
252
+ }),
253
+ appendCompaction: (input) => enqueue(async () => {
254
+ if (records.length === 0 || activeId === null)
255
+ throw new Error('Cannot compact an empty native transcript');
256
+ if (input.summary.trim().length === 0)
257
+ throw new Error('Native compact summary must not be blank');
258
+ for (const value of [
259
+ input.preTokens,
260
+ input.postTokens,
261
+ input.durationMs,
262
+ ]) {
263
+ if (!Number.isFinite(value) || value < 0)
264
+ throw new Error('Native compact accounting values must be non-negative');
265
+ }
266
+ const events = records.map((record) => record.event);
267
+ if (unresolvedActiveToolCallIds(projectActiveMessages(events, activeId)).length > 0)
268
+ throw new Error('Cannot compact a native transcript with unresolved tool calls');
269
+ const boundaryId = this.createId();
270
+ const summaryId = this.createId();
271
+ const boundary = {
272
+ kind: 'context-boundary',
273
+ id: boundaryId,
274
+ parentId: null,
275
+ sessionId: this.sessionId,
276
+ timestamp: this.now(),
277
+ logicalParentId: activeId,
278
+ trigger: input.trigger,
279
+ preTokens: input.preTokens,
280
+ postTokens: input.postTokens,
281
+ durationMs: input.durationMs,
282
+ };
283
+ const summary = {
284
+ kind: 'context-summary',
285
+ id: summaryId,
286
+ parentId: boundaryId,
287
+ sessionId: this.sessionId,
288
+ timestamp: this.now(),
289
+ summary: input.summary,
290
+ };
291
+ const suffix = input.preservedMessages ?? [];
292
+ const replayToolCallIds = new Map();
293
+ for (const message of suffix) {
294
+ if (message.role !== 'assistant')
295
+ continue;
296
+ for (const call of message.toolCalls ?? [])
297
+ replayToolCallIds.set(call.id, this.createId());
298
+ }
299
+ const replaySuffix = suffix.map((message) => {
300
+ if (message.role === 'assistant')
301
+ return {
302
+ ...message,
303
+ ...(message.toolCalls === undefined
304
+ ? {}
305
+ : {
306
+ toolCalls: message.toolCalls.map((call) => ({
307
+ ...call,
308
+ id: replayToolCallIds.get(call.id) ?? call.id,
309
+ })),
310
+ }),
311
+ };
312
+ if (message.role === 'tool')
313
+ return {
314
+ ...message,
315
+ toolCallId: replayToolCallIds.get(message.toolCallId) ??
316
+ message.toolCallId,
317
+ };
318
+ return { ...message };
319
+ });
320
+ const suffixEvent = replaySuffix.length
321
+ ? {
322
+ kind: 'messages',
323
+ id: this.createId(),
324
+ parentId: summaryId,
325
+ sessionId: this.sessionId,
326
+ timestamp: this.now(),
327
+ messages: replaySuffix,
328
+ }
329
+ : undefined;
330
+ const compactedEvents = [
331
+ boundary,
332
+ summary,
333
+ ...(suffixEvent === undefined ? [] : [suffixEvent]),
334
+ ];
335
+ const appended = await nativeLease.appendMany(tail, compactedEvents);
336
+ if (appended.status === 'conflict')
337
+ throw new Error(`native transcript append conflict: ${appended.reason}`);
338
+ records.push({ event: boundary }, { event: summary }, ...(suffixEvent === undefined ? [] : [{ event: suffixEvent }]));
339
+ tail = appended.tail;
340
+ activeId = suffixEvent?.id ?? summaryId;
341
+ return { boundaryId, summaryId };
342
+ }),
343
+ };
344
+ return operation(lease);
345
+ });
346
+ if (result.status === 'conflict')
347
+ throw new Error(`native transcript lease conflict: ${result.reason}`);
348
+ return result.value;
349
+ }
350
+ }
351
+ //# sourceMappingURL=native-session-transcript.js.map
@@ -0,0 +1,34 @@
1
+ import { type NativeSessionTranscriptLease } from './native-session-transcript.js';
2
+ export type NativeSidechainPermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'default' | 'dontAsk' | 'plan';
3
+ export interface NativeSidechainMetadata {
4
+ readonly agentType: string;
5
+ readonly description: string;
6
+ readonly toolUseId: string;
7
+ readonly spawnDepth: number;
8
+ readonly cwd: string;
9
+ readonly promptId: string;
10
+ readonly name?: string;
11
+ readonly permissionMode?: NativeSidechainPermissionMode;
12
+ readonly isolation?: 'worktree';
13
+ readonly parentAgentId?: string;
14
+ readonly worktreePath?: string;
15
+ }
16
+ export interface NativeSidechainPaths {
17
+ readonly sessionId: string;
18
+ readonly agentId: string;
19
+ readonly directory: string;
20
+ readonly transcriptFile: string;
21
+ readonly metadataFile: string;
22
+ readonly lockFile?: string;
23
+ }
24
+ export declare class NativeSidechainTranscript {
25
+ private readonly paths;
26
+ private readonly transcript;
27
+ private readonly store;
28
+ constructor(paths: NativeSidechainPaths);
29
+ create(prompt: string, metadata: NativeSidechainMetadata): Promise<void>;
30
+ withLease<T>(operation: (lease: NativeSessionTranscriptLease) => Promise<T>): Promise<T>;
31
+ loadReadOnly(): Promise<import("../persistence/native-transcript-store.js").NativeTranscriptRecovery>;
32
+ metadata(): Promise<NativeSidechainMetadata>;
33
+ }
34
+ //# sourceMappingURL=native-sidechain-transcript.d.ts.map