praxis-agent 0.36.0 → 0.38.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 (145) hide show
  1. package/README.md +42 -4
  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 +17 -0
  9. package/dist/application/session-service.js +612 -70
  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 +32 -0
  13. package/dist/application/team-agent-runtime.js +186 -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-decision-surface.d.ts +22 -0
  17. package/dist/application/team-lead-decision-surface.js +16 -0
  18. package/dist/application/team-lead-operations.d.ts +38 -0
  19. package/dist/application/team-lead-operations.js +155 -0
  20. package/dist/application/team-mailbox.d.ts +54 -0
  21. package/dist/application/team-mailbox.js +196 -0
  22. package/dist/application/team-manager.d.ts +113 -0
  23. package/dist/application/team-manager.js +681 -0
  24. package/dist/application/team-observability.d.ts +113 -0
  25. package/dist/application/team-observability.js +312 -0
  26. package/dist/application/team-workspace.d.ts +29 -0
  27. package/dist/application/team-workspace.js +197 -0
  28. package/dist/application/transcript-projection.d.ts +2 -0
  29. package/dist/application/transcript-projection.js +30 -1
  30. package/dist/cli/interactive.d.ts +7 -0
  31. package/dist/cli/interactive.js +783 -277
  32. package/dist/cli/tui/btw-surface-model.d.ts +22 -0
  33. package/dist/cli/tui/btw-surface-model.js +10 -0
  34. package/dist/cli/tui/claude-style.d.ts +58 -111
  35. package/dist/cli/tui/claude-style.js +252 -235
  36. package/dist/cli/tui/command-palette-model.d.ts +33 -0
  37. package/dist/cli/tui/command-palette-model.js +44 -0
  38. package/dist/cli/tui/config-dashboard.d.ts +3 -9
  39. package/dist/cli/tui/config-dashboard.js +2 -1
  40. package/dist/cli/tui/config-surface-model.d.ts +24 -0
  41. package/dist/cli/tui/config-surface-model.js +16 -0
  42. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  43. package/dist/cli/tui/decision-surface-model.js +131 -0
  44. package/dist/cli/tui/decision-surface.d.ts +7 -0
  45. package/dist/cli/tui/decision-surface.js +41 -0
  46. package/dist/cli/tui/diff-surface-model.d.ts +83 -0
  47. package/dist/cli/tui/diff-surface-model.js +142 -0
  48. package/dist/cli/tui/doctor-dashboard.d.ts +3 -4
  49. package/dist/cli/tui/doctor-dashboard.js +2 -1
  50. package/dist/cli/tui/doctor-surface-model.d.ts +13 -0
  51. package/dist/cli/tui/doctor-surface-model.js +9 -0
  52. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  53. package/dist/cli/tui/help-surface-model.js +131 -0
  54. package/dist/cli/tui/hooks-surface-model.d.ts +18 -0
  55. package/dist/cli/tui/hooks-surface-model.js +11 -0
  56. package/dist/cli/tui/leaf-surface-model.d.ts +68 -0
  57. package/dist/cli/tui/leaf-surface-model.js +55 -0
  58. package/dist/cli/tui/list-surface-model.d.ts +27 -0
  59. package/dist/cli/tui/list-surface-model.js +22 -0
  60. package/dist/cli/tui/mcp-elicitation-surface-model.d.ts +27 -0
  61. package/dist/cli/tui/mcp-elicitation-surface-model.js +25 -0
  62. package/dist/cli/tui/mcp-elicitation.d.ts +7 -1
  63. package/dist/cli/tui/mcp-elicitation.js +6 -2
  64. package/dist/cli/tui/mcp-panel.d.ts +3 -4
  65. package/dist/cli/tui/mcp-panel.js +3 -3
  66. package/dist/cli/tui/mcp-surface-model.d.ts +11 -0
  67. package/dist/cli/tui/mcp-surface-model.js +4 -0
  68. package/dist/cli/tui/memory-surface-model.d.ts +20 -0
  69. package/dist/cli/tui/memory-surface-model.js +12 -0
  70. package/dist/cli/tui/mention-picker-model.d.ts +41 -0
  71. package/dist/cli/tui/mention-picker-model.js +53 -0
  72. package/dist/cli/tui/model-effort-surface-model.d.ts +29 -0
  73. package/dist/cli/tui/model-effort-surface-model.js +38 -0
  74. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  75. package/dist/cli/tui/permission-surface-model.js +360 -0
  76. package/dist/cli/tui/permission-surface.d.ts +7 -0
  77. package/dist/cli/tui/permission-surface.js +69 -0
  78. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  79. package/dist/cli/tui/presentation-environment.js +149 -0
  80. package/dist/cli/tui/rewind-surface-model.d.ts +53 -0
  81. package/dist/cli/tui/rewind-surface-model.js +54 -0
  82. package/dist/cli/tui/sandbox-dashboard.d.ts +3 -4
  83. package/dist/cli/tui/sandbox-dashboard.js +2 -1
  84. package/dist/cli/tui/sandbox-surface-model.d.ts +13 -0
  85. package/dist/cli/tui/sandbox-surface-model.js +4 -0
  86. package/dist/cli/tui/session-picker-model.d.ts +41 -0
  87. package/dist/cli/tui/session-picker-model.js +69 -0
  88. package/dist/cli/tui/task-panel.d.ts +3 -3
  89. package/dist/cli/tui/task-panel.js +2 -1
  90. package/dist/cli/tui/task-surface-model.d.ts +11 -0
  91. package/dist/cli/tui/task-surface-model.js +4 -0
  92. package/dist/cli/tui/theme-surface-model.d.ts +85 -0
  93. package/dist/cli/tui/theme-surface-model.js +52 -0
  94. package/dist/cli/tui/tool-permission.js +7 -3
  95. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  96. package/dist/cli/tui/transcript-presentation.js +26 -6
  97. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  98. package/dist/cli/tui/transcript-viewport.js +517 -111
  99. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  100. package/dist/cli/tui/transcript-window-model.js +772 -0
  101. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  102. package/dist/cli/tui/tui-screen-model.js +210 -0
  103. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  104. package/dist/cli/tui/tui-view-model.js +1 -68
  105. package/dist/cli-runtime.d.ts +2 -0
  106. package/dist/cli-runtime.js +547 -33
  107. package/dist/compatibility/claude/team.d.ts +77 -0
  108. package/dist/compatibility/claude/team.js +219 -0
  109. package/dist/compatibility/claude/transcript-codec.js +6 -0
  110. package/dist/core/permission-policy.d.ts +4 -0
  111. package/dist/core/permission-policy.js +21 -0
  112. package/dist/core/runtime.d.ts +3 -0
  113. package/dist/core/runtime.js +5 -1
  114. package/dist/core/team-mailbox.d.ts +54 -0
  115. package/dist/core/team-mailbox.js +212 -0
  116. package/dist/core/team-ownership.d.ts +84 -0
  117. package/dist/core/team-ownership.js +505 -0
  118. package/dist/core/tool-execution-scheduler.js +6 -1
  119. package/dist/core/transcript-event.d.ts +5 -1
  120. package/dist/core/transcript-event.js +12 -0
  121. package/dist/hooks/claude-file-change-watcher.d.ts +3 -0
  122. package/dist/hooks/claude-file-change-watcher.js +70 -8
  123. package/dist/hooks/claude-hook-tools.js +6 -1
  124. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  125. package/dist/persistence/claude-transcript-store.js +2 -10
  126. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  127. package/dist/persistence/native-team-mailbox.js +542 -0
  128. package/dist/persistence/native-team-store.d.ts +34 -0
  129. package/dist/persistence/native-team-store.js +257 -0
  130. package/dist/persistence/native-transcript-migration.d.ts +37 -0
  131. package/dist/persistence/native-transcript-migration.js +430 -0
  132. package/dist/persistence/native-transcript-store.d.ts +69 -0
  133. package/dist/persistence/native-transcript-store.js +321 -0
  134. package/dist/persistence/transcript-file-append.d.ts +2 -0
  135. package/dist/persistence/transcript-file-append.js +10 -0
  136. package/dist/tools/claude-capabilities.d.ts +4 -0
  137. package/dist/tools/claude-capabilities.js +18 -2
  138. package/dist/tools/local-tools.js +10 -1
  139. package/dist/tools/team-lead-tools.d.ts +33 -0
  140. package/dist/tools/team-lead-tools.js +600 -0
  141. package/dist/tools/team-member-tools.d.ts +21 -0
  142. package/dist/tools/team-member-tools.js +259 -0
  143. package/package.json +8 -2
  144. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  145. package/dist/cli/tui/fullscreen-renderer.js +0 -13
package/README.md CHANGED
@@ -38,6 +38,10 @@ Praxis does not use Claude subscription authentication. Claude Code
38
38
  interoperability covers local sessions, configuration, permissions, memory,
39
39
  skills, hooks, agents, plugins, and MCP data.
40
40
 
41
+ Claude Team compatibility is explicit and fail-closed: supported delete/send
42
+ wire shapes route through native Team operations, while lossy or unsupported
43
+ Team surfaces are rejected rather than silently simplified.
44
+
41
45
  ## Install
42
46
 
43
47
  ```sh
@@ -122,7 +126,10 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
122
126
  for supported local terminals,
123
127
  `Ctrl+V` text/image clipboard paste, `Ctrl+Z` shell suspension and `fg`
124
128
  recovery, permission-gated `!` shell turns, navigable current/per-turn Git
125
- diff views, rich decision panels, and measured context budgets; print mode,
129
+ diff views, semantic plan/question decision panels with complete
130
+ screen-reader actions, semantic screen projection across selectable surfaces,
131
+ deterministic resize-aware URL/form elicitation rendering, and measured
132
+ context budgets; print mode,
126
133
  structured JSON/JSONL, context compaction, tool loops, and bounded execution.
127
134
  - **Built-in tools** — read, write, edit, glob, search, shell, notebook, PDF,
128
135
  image, web, scheduled prompts, workflows, and worktrees.
@@ -152,7 +159,28 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
152
159
  Claude-compatible main-thread agent definitions with native prompt, model,
153
160
  tool, memory, first-turn, and resume behavior. Agent execution uses one
154
161
  durable lifecycle vocabulary with bounded cancellation and drain,
155
- continuation, notifications, and single-owner orphan recovery.
162
+ continuation, notifications, and single-owner orphan recovery. Experimental
163
+ local Teams (`PRAXIS_ENABLE_TEAMS=true`) stay absent from ordinary startup by
164
+ default and add durable task ownership plus one ordered mailbox with stable
165
+ identities, fixed broadcast recipients, durable cursors, bounded retention,
166
+ and bounded model-context projection. Teams are explicitly experimental and
167
+ opt-in: `PRAXIS_ENABLE_TEAMS=true` is required; otherwise Team code is not
168
+ loaded, discovered, or exposed. New Teams default to Hybrid lead control and
169
+ sequential execution, with optional Coordinator and Swarm policies. Commits
170
+ remain Lead-owned. Swarm admits only independent, dependency-ready,
171
+ non-conflicting tasks, within durable agent, concurrency, token, duration,
172
+ and shutdown-drain budgets.
173
+ Child permissions can only tighten the parent; concurrent asks form one FIFO
174
+ Lead Decision queue with provenance. Coordinator leads are restricted to
175
+ orchestration, and custom Team agents receive no MCP capability. The native
176
+ CLI also exposes `praxis team status`, `logs`, and `attach` in human or JSON
177
+ form; durable-local attach does not require tmux. The Claude Team adapter is
178
+ removable and currently limited to fixture-verified delete/send; Claude create
179
+ is decoded but rejected when the payload cannot represent native roster/task
180
+ claims,
181
+ shutdown, and plan-response shapes; native task, notification, context, and
182
+ Team resume/inbox seams are implemented, while their Claude-facing zero-skip
183
+ qualification remains explicitly separate.
156
184
  - **Claude-compatible ecosystem** — shared instructions with recursive `@`
157
185
  imports, memory, skills, commands, agents, hooks, settings, MCP servers,
158
186
  plugins, and transcript data.
@@ -240,9 +268,19 @@ npm ci
240
268
  npm run check
241
269
  ```
242
270
 
243
- `npm run build:native` compiles the current Praxis-owned core, provider
244
- adapters, and native data-plane slice without the Claude compatibility adapter.
271
+ `npm run build:native` compiles the implemented Praxis-owned core, provider,
272
+ native transcript, and session profile without the Claude compatibility adapter.
273
+ `npm run test:native:deletion` adds an emitted-output deletion gate. These are
274
+ implemented profile checks, not qualification of the full native package.
275
+ Native transcript migration remains explicit and recoverable. The built-CLI
276
+ smoke gate `npm run verify:native-migration-cli` exercises all-session dry-run,
277
+ publication, text/JSON idempotence, rollback, and Claude-data-plane exclusion.
278
+ The isolated active-stream regression proof is available through
279
+ `npm run test:performance:active-stream`; the product p95 budget remains 50 ms.
245
280
  `npm run check` also enforces the corresponding source dependency direction.
281
+ `npm run test:core-completion` runs the 56-story #402 audit and reports
282
+ implemented, qualified, blocked, deferred, and out-of-scope states separately;
283
+ it never treats missing live prerequisites as a pass.
246
284
 
247
285
  Contributions use Conventional Commit pull-request titles and the protected
248
286
  squash-merge workflow. Read
@@ -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