praxis-agent 0.34.0 → 0.35.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 (49) hide show
  1. package/README.md +4 -0
  2. package/dist/application/session-service.d.ts +0 -1
  3. package/dist/application/session-service.js +1619 -1617
  4. package/dist/application/subagent-service.js +27 -16
  5. package/dist/application/turn-lifecycle.d.ts +38 -0
  6. package/dist/application/turn-lifecycle.js +36 -0
  7. package/dist/cli/interactive.js +17 -7
  8. package/dist/cli/protocol.js +5 -2
  9. package/dist/cli/tui/claude-style.d.ts +5 -48
  10. package/dist/cli/tui/claude-style.js +19 -73
  11. package/dist/cli/tui/transcript-presentation.d.ts +89 -0
  12. package/dist/cli/tui/transcript-presentation.js +169 -0
  13. package/dist/cli/tui/transcript-viewport.d.ts +5 -34
  14. package/dist/cli/tui/transcript-viewport.js +639 -158
  15. package/dist/cli/tui/tui-view-model.d.ts +6 -2
  16. package/dist/cli/tui/tui-view-model.js +23 -6
  17. package/dist/compatibility/claude/context.d.ts +2 -2
  18. package/dist/compatibility/claude/context.js +2 -14
  19. package/dist/compatibility/claude/data-plane-adapter.d.ts +9 -0
  20. package/dist/compatibility/claude/data-plane-adapter.js +44 -0
  21. package/dist/compatibility/claude/paths.js +2 -2
  22. package/dist/compatibility/claude/shared-resources.d.ts +7 -38
  23. package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
  24. package/dist/compatibility/claude/transcript-codec.js +512 -0
  25. package/dist/core/context.d.ts +25 -7
  26. package/dist/core/context.js +46 -0
  27. package/dist/core/prompt-composer.d.ts +8 -16
  28. package/dist/core/prompt-composer.js +65 -25
  29. package/dist/core/resources.d.ts +39 -0
  30. package/dist/core/resources.js +2 -0
  31. package/dist/core/runtime.d.ts +1 -1
  32. package/dist/core/session.d.ts +2 -0
  33. package/dist/core/session.js +5 -0
  34. package/dist/core/transcript-codec.d.ts +70 -0
  35. package/dist/core/transcript-codec.js +60 -0
  36. package/dist/core/transcript-event.d.ts +33 -0
  37. package/dist/core/transcript-event.js +198 -0
  38. package/dist/persistence/data-plane-adapter.d.ts +32 -0
  39. package/dist/persistence/data-plane-adapter.js +2 -0
  40. package/dist/persistence/data-plane.d.ts +4 -20
  41. package/dist/persistence/data-plane.js +10 -47
  42. package/dist/persistence/native-data-plane-adapter.d.ts +9 -0
  43. package/dist/persistence/native-data-plane-adapter.js +39 -0
  44. package/dist/persistence/native-resources.d.ts +4 -4
  45. package/dist/persistence/native-transcript-codec.d.ts +13 -0
  46. package/dist/persistence/native-transcript-codec.js +102 -0
  47. package/dist/tools/claude-user-message.d.ts +0 -1
  48. package/dist/tools/claude-user-message.js +0 -1
  49. package/package.json +6 -2
@@ -0,0 +1,512 @@
1
+ import { isModelDocument, isModelImage, isTranscriptEvent, } from '../../core/transcript-event.js';
2
+ import { formatClaudeCompactSummary, parseClaudeCompactSummary, } from './compaction.js';
3
+ import { selectClaudeSchemaAdapter, } from './schema.js';
4
+ import { createPreservationToken, readPreservationToken, decodeDocumentWith, diagnostic, } from '../../core/transcript-codec.js';
5
+ const TOKEN_ID = Symbol('claude.transcript');
6
+ const isString = (value) => typeof value === 'string';
7
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ const decodeIdentity = (entry) => {
9
+ if (!isString(entry.uuid) ||
10
+ !isString(entry.sessionId) ||
11
+ !isString(entry.timestamp) ||
12
+ !isString(entry.version) ||
13
+ !Object.prototype.hasOwnProperty.call(entry, 'parentUuid'))
14
+ return null;
15
+ if (entry.parentUuid !== null && !isString(entry.parentUuid))
16
+ return null;
17
+ return {
18
+ id: entry.uuid,
19
+ parentId: entry.parentUuid,
20
+ sessionId: entry.sessionId,
21
+ timestamp: entry.timestamp,
22
+ };
23
+ };
24
+ function decodeMediaBlock(block) {
25
+ if ((block.type !== 'image' && block.type !== 'document') ||
26
+ !isRecord(block.source) ||
27
+ block.source.type !== 'base64' ||
28
+ !isString(block.source.media_type) ||
29
+ !isString(block.source.data))
30
+ return null;
31
+ const media = {
32
+ type: block.type,
33
+ mediaType: block.source.media_type,
34
+ data: block.source.data,
35
+ };
36
+ return isModelImage(media) || isModelDocument(media) ? media : null;
37
+ }
38
+ function decodeContentBlocks(content) {
39
+ if (content.length === 0)
40
+ return null;
41
+ const contentBlocks = [];
42
+ let contentText = '';
43
+ for (const raw of content) {
44
+ if (!isRecord(raw))
45
+ return null;
46
+ if (raw.type === 'text' && isString(raw.text)) {
47
+ contentText += raw.text;
48
+ contentBlocks.push({ type: 'text', text: raw.text });
49
+ continue;
50
+ }
51
+ const media = decodeMediaBlock(raw);
52
+ if (!media)
53
+ return null;
54
+ contentBlocks.push(media);
55
+ }
56
+ return { content: contentText, contentBlocks };
57
+ }
58
+ function userEvent(entry) {
59
+ const identity = decodeIdentity(entry);
60
+ if (!identity || !isRecord(entry.message) || entry.message.role !== 'user')
61
+ return null;
62
+ const content = entry.message.content;
63
+ if (isString(content)) {
64
+ return {
65
+ kind: 'messages',
66
+ ...identity,
67
+ messages: [{ role: 'user', content }],
68
+ };
69
+ }
70
+ if (!Array.isArray(content) || content.length === 0)
71
+ return null;
72
+ const messages = [];
73
+ let pendingUserBlocks = [];
74
+ const flushUser = () => {
75
+ if (pendingUserBlocks.length === 0)
76
+ return;
77
+ messages.push({
78
+ role: 'user',
79
+ content: pendingUserBlocks
80
+ .filter((block) => block.type === 'text')
81
+ .map((block) => block.text)
82
+ .join(''),
83
+ contentBlocks: pendingUserBlocks,
84
+ });
85
+ pendingUserBlocks = [];
86
+ };
87
+ for (const raw of content) {
88
+ if (!isRecord(raw))
89
+ return null;
90
+ if (raw.type === 'tool_result' && isString(raw.tool_use_id)) {
91
+ flushUser();
92
+ if (isString(raw.content)) {
93
+ messages.push({
94
+ role: 'tool',
95
+ toolCallId: raw.tool_use_id,
96
+ content: raw.content,
97
+ isError: raw.is_error === true,
98
+ });
99
+ continue;
100
+ }
101
+ if (!Array.isArray(raw.content))
102
+ return null;
103
+ const decoded = decodeContentBlocks(raw.content);
104
+ if (!decoded)
105
+ return null;
106
+ messages.push({
107
+ role: 'tool',
108
+ toolCallId: raw.tool_use_id,
109
+ ...decoded,
110
+ isError: raw.is_error === true,
111
+ });
112
+ continue;
113
+ }
114
+ if (raw.type === 'text' && isString(raw.text)) {
115
+ pendingUserBlocks.push({ type: 'text', text: raw.text });
116
+ continue;
117
+ }
118
+ const media = decodeMediaBlock(raw);
119
+ if (!media)
120
+ return null;
121
+ pendingUserBlocks.push(media);
122
+ }
123
+ flushUser();
124
+ return messages.length ? { kind: 'messages', ...identity, messages } : null;
125
+ }
126
+ function assistantEvent(entry) {
127
+ const identity = decodeIdentity(entry);
128
+ if (!identity ||
129
+ !isRecord(entry.message) ||
130
+ entry.message.role !== 'assistant' ||
131
+ !Array.isArray(entry.message.content))
132
+ return null;
133
+ let content = '';
134
+ const thinkingBlocks = [];
135
+ const toolCalls = [];
136
+ for (const raw of entry.message.content) {
137
+ if (!isRecord(raw))
138
+ return null;
139
+ if (raw.type === 'text' && isString(raw.text))
140
+ content += raw.text;
141
+ else if (raw.type === 'thinking' &&
142
+ isString(raw.thinking) &&
143
+ isString(raw.signature))
144
+ thinkingBlocks.push({
145
+ type: 'thinking',
146
+ thinking: raw.thinking,
147
+ signature: raw.signature,
148
+ });
149
+ else if (raw.type === 'redacted_thinking' && isString(raw.data))
150
+ thinkingBlocks.push({ type: 'redacted_thinking', data: raw.data });
151
+ else if (raw.type === 'tool_use' &&
152
+ isString(raw.id) &&
153
+ isString(raw.name) &&
154
+ isRecord(raw.input))
155
+ toolCalls.push({ id: raw.id, name: raw.name, input: raw.input });
156
+ else
157
+ return null;
158
+ }
159
+ let result = {
160
+ kind: 'messages',
161
+ ...identity,
162
+ messages: [
163
+ {
164
+ role: 'assistant',
165
+ content,
166
+ ...(thinkingBlocks.length ? { thinkingBlocks } : {}),
167
+ ...(toolCalls.length ? { toolCalls } : {}),
168
+ },
169
+ ],
170
+ };
171
+ if (isString(entry.message.model))
172
+ result = { ...result, model: entry.message.model };
173
+ const stop = entry.message.stop_reason;
174
+ if (stop === 'end_turn' || stop === 'tool_use' || stop === 'max_tokens')
175
+ result = { ...result, terminalReason: stop };
176
+ return result;
177
+ }
178
+ function encodeMediaBlock(media) {
179
+ return {
180
+ type: media.type,
181
+ source: {
182
+ type: 'base64',
183
+ media_type: media.mediaType,
184
+ data: media.data,
185
+ },
186
+ };
187
+ }
188
+ function encodeCanonicalContent(message) {
189
+ if (message.contentBlocks !== undefined) {
190
+ const textContent = message.contentBlocks
191
+ .filter((block) => block.type === 'text')
192
+ .map((block) => block.text)
193
+ .join('');
194
+ if (textContent !== message.content) {
195
+ throw new Error('Canonical content blocks disagree with message content');
196
+ }
197
+ const images = message.contentBlocks.filter((block) => block.type === 'image');
198
+ const documents = message.contentBlocks.filter((block) => block.type === 'document');
199
+ if ((message.images !== undefined &&
200
+ JSON.stringify(message.images) !== JSON.stringify(images)) ||
201
+ (message.documents !== undefined &&
202
+ JSON.stringify(message.documents) !== JSON.stringify(documents))) {
203
+ throw new Error('Canonical content blocks disagree with message media arrays');
204
+ }
205
+ return message.contentBlocks.map((block) => block.type === 'text'
206
+ ? { type: 'text', text: block.text }
207
+ : encodeMediaBlock(block));
208
+ }
209
+ const blocks = [];
210
+ if (message.content.length > 0)
211
+ blocks.push({ type: 'text', text: message.content });
212
+ blocks.push(...(message.images ?? []).map(encodeMediaBlock), ...(message.documents ?? []).map(encodeMediaBlock));
213
+ return blocks;
214
+ }
215
+ function encodeUserBatch(event) {
216
+ if (event.model !== undefined || event.terminalReason !== undefined) {
217
+ throw new Error('User and tool batches cannot carry assistant metadata');
218
+ }
219
+ if (event.messages.length === 1 &&
220
+ event.messages[0]?.role === 'user' &&
221
+ event.messages[0].contentBlocks === undefined &&
222
+ event.messages[0].images === undefined &&
223
+ event.messages[0].documents === undefined) {
224
+ return {
225
+ type: 'user',
226
+ message: { role: 'user', content: event.messages[0].content },
227
+ };
228
+ }
229
+ const content = [];
230
+ let previousWasUser = false;
231
+ for (const message of event.messages) {
232
+ if (message.role === 'user') {
233
+ if (previousWasUser) {
234
+ throw new Error('Adjacent user messages cannot be represented by one Claude entry');
235
+ }
236
+ const blocks = encodeCanonicalContent(message);
237
+ if (blocks.length === 0) {
238
+ throw new Error('An empty user message cannot be represented inside a Claude batch');
239
+ }
240
+ content.push(...blocks);
241
+ previousWasUser = true;
242
+ continue;
243
+ }
244
+ if (message.role !== 'tool') {
245
+ throw new Error('Claude message batches must be all user/tool messages');
246
+ }
247
+ const blocks = encodeCanonicalContent(message);
248
+ if (blocks.some((block) => block.type !== 'text')) {
249
+ throw new Error('Claude tool media requires provider metadata unavailable in TranscriptEvent');
250
+ }
251
+ content.push({
252
+ type: 'tool_result',
253
+ tool_use_id: message.toolCallId,
254
+ content: message.contentBlocks === undefined &&
255
+ (message.images?.length ?? 0) === 0 &&
256
+ (message.documents?.length ?? 0) === 0
257
+ ? message.content
258
+ : blocks,
259
+ is_error: message.isError,
260
+ });
261
+ previousWasUser = false;
262
+ }
263
+ return { type: 'user', message: { role: 'user', content } };
264
+ }
265
+ function emptyAssistantUsage() {
266
+ return {
267
+ input_tokens: 0,
268
+ cache_creation_input_tokens: 0,
269
+ cache_read_input_tokens: 0,
270
+ output_tokens: 0,
271
+ server_tool_use: {
272
+ web_search_requests: 0,
273
+ web_fetch_requests: 0,
274
+ },
275
+ service_tier: 'standard',
276
+ cache_creation: {
277
+ ephemeral_1h_input_tokens: 0,
278
+ ephemeral_5m_input_tokens: 0,
279
+ },
280
+ inference_geo: '',
281
+ };
282
+ }
283
+ function encodeAssistant(event) {
284
+ if (event.messages.length !== 1 || event.messages[0]?.role !== 'assistant') {
285
+ throw new Error('A Claude assistant entry requires one assistant message');
286
+ }
287
+ if (!event.model) {
288
+ throw new Error('A Claude assistant entry requires a model');
289
+ }
290
+ if (event.terminalReason === 'prompt_too_long') {
291
+ throw new Error('prompt_too_long is not a persisted assistant terminal');
292
+ }
293
+ const message = event.messages[0];
294
+ const content = [...(message.thinkingBlocks ?? [])];
295
+ if (message.content.length > 0)
296
+ content.push({ type: 'text', text: message.content });
297
+ content.push(...(message.toolCalls ?? []).map((call) => ({
298
+ type: 'tool_use',
299
+ id: call.id,
300
+ name: call.name,
301
+ input: call.input,
302
+ })));
303
+ if (content.length === 0) {
304
+ throw new Error('A Claude assistant entry requires persisted content');
305
+ }
306
+ return {
307
+ type: 'assistant',
308
+ message: {
309
+ id: event.id,
310
+ type: 'message',
311
+ role: 'assistant',
312
+ model: event.model,
313
+ content,
314
+ stop_reason: event.terminalReason ??
315
+ (message.toolCalls?.length ? 'tool_use' : 'end_turn'),
316
+ stop_sequence: null,
317
+ usage: emptyAssistantUsage(),
318
+ stop_details: null,
319
+ },
320
+ };
321
+ }
322
+ export class ClaudeTranscriptCodec {
323
+ options;
324
+ id = 'claude';
325
+ version;
326
+ writeMode;
327
+ adapter;
328
+ constructor(options) {
329
+ this.options = options;
330
+ this.version = options.version;
331
+ this.adapter = selectClaudeSchemaAdapter(options.version);
332
+ this.writeMode = this.adapter.writeMode;
333
+ }
334
+ decodeLine(line, lineNumber = 1, byteOffset = 0) {
335
+ let entry;
336
+ try {
337
+ entry = this.adapter.parse(line);
338
+ }
339
+ catch (error) {
340
+ return {
341
+ ok: false,
342
+ issue: diagnostic('corrupt-line', error instanceof Error
343
+ ? error.message
344
+ : 'Invalid Claude transcript line', byteOffset, lineNumber, { schemaVersion: this.version }),
345
+ };
346
+ }
347
+ const identity = decodeIdentity(entry);
348
+ let event = null;
349
+ if (entry.type === 'system' &&
350
+ entry.subtype === 'compact_boundary' &&
351
+ identity &&
352
+ isString(entry.logicalParentUuid) &&
353
+ isRecord(entry.compactMetadata)) {
354
+ const m = entry.compactMetadata;
355
+ if ((m.trigger === 'auto' || m.trigger === 'manual') &&
356
+ typeof m.preTokens === 'number' &&
357
+ typeof m.postTokens === 'number' &&
358
+ typeof m.durationMs === 'number' &&
359
+ [m.preTokens, m.postTokens, m.durationMs].every((v) => Number.isFinite(v) && v >= 0))
360
+ event = {
361
+ kind: 'context-boundary',
362
+ ...identity,
363
+ logicalParentId: entry.logicalParentUuid,
364
+ trigger: m.trigger,
365
+ preTokens: m.preTokens,
366
+ postTokens: m.postTokens,
367
+ durationMs: m.durationMs,
368
+ };
369
+ }
370
+ else if (entry.type === 'user' &&
371
+ entry.isCompactSummary === true &&
372
+ isRecord(entry.message) &&
373
+ isString(entry.message.content) &&
374
+ identity) {
375
+ const summary = parseClaudeCompactSummary(entry.message.content);
376
+ if (summary !== null)
377
+ event = { kind: 'context-summary', ...identity, summary };
378
+ }
379
+ else if (entry.type === 'user')
380
+ event = userEvent(entry);
381
+ else if (entry.type === 'assistant')
382
+ event = assistantEvent(entry);
383
+ if (!event || !isTranscriptEvent(event))
384
+ return {
385
+ ok: false,
386
+ issue: diagnostic('unsupported-event', `Unsupported Claude transcript entry type ${entry.type}`, byteOffset, lineNumber, { eventKind: entry.type }),
387
+ };
388
+ return {
389
+ ok: true,
390
+ record: {
391
+ event,
392
+ preservation: createPreservationToken(TOKEN_ID, {
393
+ owner: TOKEN_ID,
394
+ raw: line,
395
+ event,
396
+ }),
397
+ },
398
+ };
399
+ }
400
+ encodeLine(event, preservation) {
401
+ if (this.writeMode === 'read-only')
402
+ return {
403
+ ok: false,
404
+ issue: diagnostic('unsupported-version', `Unsupported Claude Code transcript version ${this.version}`, 0, null, { schemaVersion: this.version }),
405
+ };
406
+ if (!isTranscriptEvent(event))
407
+ return {
408
+ ok: false,
409
+ issue: diagnostic('unsupported-event', 'Invalid TranscriptEvent', 0, null),
410
+ };
411
+ if (preservation) {
412
+ const token = readPreservationToken(preservation, TOKEN_ID);
413
+ if (!token)
414
+ return {
415
+ ok: false,
416
+ issue: diagnostic('unsupported-event', 'Preservation token belongs to another codec', 0, null),
417
+ };
418
+ if (token.eventFingerprint === JSON.stringify(event))
419
+ return { ok: true, line: token.raw };
420
+ }
421
+ const common = {
422
+ uuid: event.id,
423
+ parentUuid: event.parentId,
424
+ sessionId: event.sessionId,
425
+ timestamp: event.timestamp,
426
+ cwd: this.options.cwd,
427
+ version: this.version,
428
+ gitBranch: this.options.gitBranch ?? null,
429
+ isSidechain: false,
430
+ userType: 'external',
431
+ entrypoint: this.options.entrypoint ?? 'cli',
432
+ };
433
+ let entry;
434
+ if (event.kind === 'context-summary')
435
+ entry = {
436
+ ...common,
437
+ type: 'user',
438
+ promptId: event.id,
439
+ isCompactSummary: true,
440
+ isVisibleInTranscriptOnly: true,
441
+ message: {
442
+ role: 'user',
443
+ content: formatClaudeCompactSummary(event.summary),
444
+ },
445
+ };
446
+ else if (event.kind === 'context-boundary') {
447
+ if (event.parentId !== null)
448
+ return {
449
+ ok: false,
450
+ issue: diagnostic('unsupported-event', 'Claude compact boundaries require a null parent', 0, null, { eventKind: event.kind }),
451
+ };
452
+ entry = {
453
+ ...common,
454
+ type: 'system',
455
+ subtype: 'compact_boundary',
456
+ content: 'Conversation compacted',
457
+ parentUuid: null,
458
+ isMeta: false,
459
+ level: 'info',
460
+ logicalParentUuid: event.logicalParentId,
461
+ compactMetadata: {
462
+ trigger: event.trigger,
463
+ preTokens: event.preTokens,
464
+ postTokens: event.postTokens,
465
+ durationMs: event.durationMs,
466
+ cumulativeDroppedTokens: Math.max(0, event.preTokens - event.postTokens),
467
+ preservedSegment: {
468
+ headUuid: event.logicalParentId,
469
+ anchorUuid: event.logicalParentId,
470
+ tailUuid: event.logicalParentId,
471
+ },
472
+ preservedMessages: {
473
+ anchorUuid: event.logicalParentId,
474
+ uuids: [event.logicalParentId],
475
+ allUuids: [event.logicalParentId],
476
+ },
477
+ },
478
+ };
479
+ }
480
+ else
481
+ try {
482
+ entry = {
483
+ ...common,
484
+ ...(event.messages[0]?.role === 'assistant'
485
+ ? encodeAssistant(event)
486
+ : encodeUserBatch(event)),
487
+ };
488
+ }
489
+ catch (error) {
490
+ return {
491
+ ok: false,
492
+ issue: diagnostic('unsupported-event', error instanceof Error ? error.message : 'Unsupported Claude event', 0, null, { eventKind: event.kind }),
493
+ };
494
+ }
495
+ try {
496
+ return { ok: true, line: this.adapter.serializeForAppend(entry) };
497
+ }
498
+ catch (error) {
499
+ return {
500
+ ok: false,
501
+ issue: diagnostic('unsupported-event', error instanceof Error ? error.message : 'Unsupported Claude event', 0, null, { eventKind: event.kind }),
502
+ };
503
+ }
504
+ }
505
+ decodeDocument(source) {
506
+ return decodeDocumentWith(this, source);
507
+ }
508
+ }
509
+ export function createClaudeTranscriptCodec(options) {
510
+ return new ClaudeTranscriptCodec(options);
511
+ }
512
+ //# sourceMappingURL=transcript-codec.js.map
@@ -1,20 +1,36 @@
1
1
  import type { ModelMessage } from './runtime.js';
2
- import type { PromptCompositionMode, PromptSection } from './prompt-composer.js';
3
2
  export type SystemContextMessage = Extract<ModelMessage, {
4
3
  role: 'system';
5
4
  }>;
6
- export interface AssembledContext {
5
+ export type ContextSectionStability = 'static' | 'session' | 'volatile';
6
+ export type ContextSectionPlacement = 'system' | 'first-user';
7
+ export type ContextCompositionMode = 'default' | 'custom' | 'bare' | 'agent' | 'subagent';
8
+ export interface ContextSection {
9
+ id: string;
10
+ content: string;
11
+ placement: ContextSectionPlacement;
12
+ stability: ContextSectionStability;
13
+ }
14
+ export interface ContextSnapshot {
15
+ sections: readonly ContextSection[];
16
+ }
17
+ export interface ContextProjection {
7
18
  systemMessages: readonly SystemContextMessage[];
8
19
  firstUserMessageContext?: string;
9
- promptSections?: readonly PromptSection[];
10
- stableSystemSectionCount?: number;
20
+ stableSystemSectionCount: number;
21
+ }
22
+ export interface TurnContextInputs {
23
+ planMode?: string;
24
+ sessionMemory?: string;
25
+ briefOutput?: boolean;
26
+ structuredOutput?: boolean;
11
27
  }
12
28
  export interface ContextAssemblyOptions {
13
29
  cwd?: string;
14
30
  lifecycleId?: string;
15
- mode?: PromptCompositionMode;
31
+ mode?: ContextCompositionMode;
16
32
  baseSystemPrompt?: string;
17
- additionalSections?: readonly PromptSection[];
33
+ turn?: TurnContextInputs;
18
34
  }
19
35
  export type ContextInvalidationReason = 'clear' | 'compact' | 'fork' | 'resource-reload' | 'restore' | 'tool-pool' | 'cwd' | 'worktree';
20
36
  export interface ContextInvalidationOptions {
@@ -22,8 +38,10 @@ export interface ContextInvalidationOptions {
22
38
  reason: ContextInvalidationReason;
23
39
  }
24
40
  export interface ContextAssembler {
25
- assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
41
+ assemble(options?: ContextAssemblyOptions): Promise<ContextSnapshot>;
26
42
  invalidate?(options: ContextInvalidationOptions): void;
27
43
  }
44
+ export declare function createContextSnapshot(sections: readonly ContextSection[]): ContextSnapshot;
45
+ export declare function projectContextSnapshot(snapshot: ContextSnapshot): ContextProjection;
28
46
  export declare function injectFirstUserMessageContext(messages: readonly ModelMessage[], context: string | undefined): ModelMessage[];
29
47
  //# sourceMappingURL=context.d.ts.map
@@ -1,3 +1,49 @@
1
+ function validateContextSections(sections) {
2
+ const identities = new Set();
3
+ let volatileSystemSeen = false;
4
+ for (const section of sections) {
5
+ if (!section.id.trim())
6
+ throw new Error('Context section id is required');
7
+ if (!section.content.trim()) {
8
+ throw new Error(`Context section ${section.id} content is required`);
9
+ }
10
+ if (identities.has(section.id)) {
11
+ throw new Error(`Duplicate context section ${section.id}`);
12
+ }
13
+ identities.add(section.id);
14
+ if (section.placement === 'system') {
15
+ if (section.stability === 'volatile')
16
+ volatileSystemSeen = true;
17
+ else if (volatileSystemSeen) {
18
+ throw new Error('Non-volatile system sections cannot follow volatile sections');
19
+ }
20
+ }
21
+ }
22
+ }
23
+ export function createContextSnapshot(sections) {
24
+ validateContextSections(sections);
25
+ return { sections: sections.map((section) => ({ ...section })) };
26
+ }
27
+ export function projectContextSnapshot(snapshot) {
28
+ const sections = snapshot.sections;
29
+ validateContextSections(sections);
30
+ const systemSections = sections.filter((section) => section.placement === 'system');
31
+ const firstVolatileSystemIndex = systemSections.findIndex((section) => section.stability === 'volatile');
32
+ const firstUserMessageContext = sections
33
+ .filter((section) => section.placement === 'first-user')
34
+ .map((section) => section.content)
35
+ .join('\n\n');
36
+ return {
37
+ systemMessages: systemSections.map((section) => ({
38
+ role: 'system',
39
+ content: section.content,
40
+ })),
41
+ stableSystemSectionCount: firstVolatileSystemIndex < 0
42
+ ? systemSections.length
43
+ : firstVolatileSystemIndex,
44
+ ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
45
+ };
46
+ }
1
47
  export function injectFirstUserMessageContext(messages, context) {
2
48
  if (context === undefined)
3
49
  return [...messages];
@@ -1,27 +1,19 @@
1
- import type { SystemContextMessage } from './context.js';
2
- export type PromptSectionStability = 'static' | 'session' | 'volatile';
3
- export type PromptSectionPlacement = 'system' | 'first-user';
4
- export type PromptCompositionMode = 'default' | 'custom' | 'bare' | 'agent' | 'subagent';
5
- export interface PromptSection {
6
- id: string;
7
- content: string;
8
- stability: PromptSectionStability;
9
- placement: PromptSectionPlacement;
10
- }
11
- export interface PromptComposition {
12
- sections: readonly PromptSection[];
13
- systemMessages: readonly SystemContextMessage[];
14
- firstUserMessageContext?: string;
15
- stableSystemSectionCount: number;
16
- }
1
+ import { type ContextAssembler, type ContextAssemblyOptions, type ContextCompositionMode, type ContextSection, type ContextSnapshot, type TurnContextInputs } from './context.js';
2
+ export type PromptSectionStability = ContextSection['stability'];
3
+ export type PromptSectionPlacement = ContextSection['placement'];
4
+ export type PromptCompositionMode = ContextCompositionMode;
5
+ export type PromptSection = ContextSection;
6
+ export type PromptComposition = ContextSnapshot;
17
7
  export interface PromptCompositionOptions {
18
8
  mode: PromptCompositionMode;
19
9
  baseSystemPrompt?: string;
20
10
  appendSystemPrompt?: string;
21
11
  sessionSections?: readonly PromptSection[];
22
12
  tailSections?: readonly PromptSection[];
13
+ turn?: TurnContextInputs;
23
14
  }
24
15
  export declare class PromptComposer {
25
16
  compose(options: PromptCompositionOptions): PromptComposition;
26
17
  }
18
+ export declare function assembleContextSnapshot(assembler: ContextAssembler | undefined, options?: ContextAssemblyOptions): Promise<ContextSnapshot>;
27
19
  //# sourceMappingURL=prompt-composer.d.ts.map