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,129 @@
1
+ import { AgentRunCancelledError, } from '../core/runtime.js';
2
+ import { contextRecoveryMadeProgress, isPromptTooLongError, } from '../core/context-budget.js';
3
+ function assertSignal(signal) {
4
+ if (signal?.aborted)
5
+ throw new AgentRunCancelledError();
6
+ }
7
+ function validateEnvelope(envelope) {
8
+ if (!Array.isArray(envelope.messages) || !Array.isArray(envelope.tools)) {
9
+ throw new TypeError('Context envelope must contain messages and tools arrays');
10
+ }
11
+ if (envelope.outputTokens !== undefined &&
12
+ (!Number.isSafeInteger(envelope.outputTokens) || envelope.outputTokens < 0)) {
13
+ throw new TypeError('Context envelope outputTokens must be nonnegative');
14
+ }
15
+ }
16
+ /** Owns context measurement, bounded recovery, and the proposal/commit seam. */
17
+ export class ContextEngine {
18
+ recoveryUsed = false;
19
+ budget;
20
+ memory;
21
+ autoCompact;
22
+ constructor(options = {}) {
23
+ this.budget = options.budget;
24
+ this.memory = options.memory ?? {};
25
+ this.autoCompact = options.autoCompact ?? true;
26
+ }
27
+ async prepare(port, signal) {
28
+ const current = port.current();
29
+ validateEnvelope(current);
30
+ if (!this.budget || !this.autoCompact)
31
+ return current;
32
+ const before = this.reportRequired(current);
33
+ if (!before.shouldCompact)
34
+ return current;
35
+ assertSignal(signal);
36
+ const irreducible = port.irreducible();
37
+ validateEnvelope(irreducible);
38
+ const irreducibleReport = this.reportRequired(irreducible);
39
+ this.budget.assertFits(irreducibleReport);
40
+ await this.memory.beforeCompact?.();
41
+ assertSignal(signal);
42
+ const proposal = await port.propose({
43
+ trigger: 'auto',
44
+ before,
45
+ irreducible: irreducibleReport,
46
+ ...(signal ? { signal } : {}),
47
+ });
48
+ assertSignal(signal);
49
+ validateEnvelope(proposal.envelope);
50
+ this.budget.assertFits(this.reportRequired(proposal.envelope));
51
+ await proposal.commit();
52
+ await this.memory.afterCompact?.();
53
+ return proposal.envelope;
54
+ }
55
+ async recover(error, port, signal) {
56
+ if (!isPromptTooLongError(error))
57
+ return { kind: 'not-applicable' };
58
+ if (this.recoveryUsed)
59
+ return { kind: 'exhausted', error };
60
+ // Recovery is a single turn-scoped opportunity, regardless of whether the
61
+ // proposal eventually proves usable. This prevents provider retry loops.
62
+ this.recoveryUsed = true;
63
+ assertSignal(signal);
64
+ if (!this.budget || !this.autoCompact) {
65
+ return { kind: 'exhausted', error };
66
+ }
67
+ try {
68
+ const current = port.current();
69
+ validateEnvelope(current);
70
+ const before = this.reportRequired(current, { promptTooLong: true });
71
+ const irreducible = port.irreducible();
72
+ validateEnvelope(irreducible);
73
+ const irreducibleReport = this.reportRequired(irreducible);
74
+ this.budget.assertFits(irreducibleReport);
75
+ await this.memory.beforeCompact?.();
76
+ assertSignal(signal);
77
+ const proposal = await port.propose({
78
+ trigger: 'reactive',
79
+ before,
80
+ irreducible: irreducibleReport,
81
+ ...(signal ? { signal } : {}),
82
+ });
83
+ assertSignal(signal);
84
+ validateEnvelope(proposal.envelope);
85
+ const after = this.reportRequired(proposal.envelope);
86
+ this.budget.assertFits(after);
87
+ if (!contextRecoveryMadeProgress({
88
+ beforeOccupancyTokens: before.occupancyTokens,
89
+ afterOccupancyTokens: after.occupancyTokens,
90
+ })) {
91
+ return { kind: 'exhausted', error };
92
+ }
93
+ await proposal.commit();
94
+ await this.memory.afterCompact?.();
95
+ return { kind: 'retry', envelope: proposal.envelope };
96
+ }
97
+ catch (cause) {
98
+ if (signal?.aborted || cause instanceof AgentRunCancelledError)
99
+ throw cause;
100
+ return { kind: 'exhausted', error };
101
+ }
102
+ }
103
+ observeUsage(usage, messages, tools) {
104
+ this.budget?.observeUsage(usage, messages, tools);
105
+ }
106
+ report(envelope, options = {}) {
107
+ validateEnvelope(envelope);
108
+ if (!this.budget) {
109
+ return undefined;
110
+ }
111
+ return this.budget.evaluate(envelope.messages, envelope.tools, {
112
+ ...(envelope.outputTokens === undefined
113
+ ? {}
114
+ : { outputTokens: envelope.outputTokens }),
115
+ ...options,
116
+ });
117
+ }
118
+ reportRequired(envelope, options = {}) {
119
+ if (!this.budget)
120
+ throw new Error('Context budget is unavailable');
121
+ return this.budget.evaluate(envelope.messages, envelope.tools, {
122
+ ...(envelope.outputTokens === undefined
123
+ ? {}
124
+ : { outputTokens: envelope.outputTokens }),
125
+ ...options,
126
+ });
127
+ }
128
+ }
129
+ //# sourceMappingURL=context-engine.js.map
@@ -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