praxis-agent 0.34.0 → 0.36.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 (78) hide show
  1. package/README.md +9 -1
  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/session-service.d.ts +8 -5
  7. package/dist/application/session-service.js +1824 -1728
  8. package/dist/application/subagent-service.js +385 -193
  9. package/dist/application/top-level-agent-manager.d.ts +1 -0
  10. package/dist/application/top-level-agent-manager.js +1025 -403
  11. package/dist/application/transcript-projection.d.ts +33 -0
  12. package/dist/application/transcript-projection.js +110 -0
  13. package/dist/application/turn-lifecycle.d.ts +40 -0
  14. package/dist/application/turn-lifecycle.js +36 -0
  15. package/dist/application/turn-memory-coordinator.d.ts +43 -0
  16. package/dist/application/turn-memory-coordinator.js +98 -0
  17. package/dist/cli/agents-dashboard.js +10 -5
  18. package/dist/cli/interactive.js +279 -209
  19. package/dist/cli/protocol.js +5 -2
  20. package/dist/cli/tui/claude-style.d.ts +5 -48
  21. package/dist/cli/tui/claude-style.js +167 -197
  22. package/dist/cli/tui/task-panel.js +10 -1
  23. package/dist/cli/tui/theme.d.ts +46 -3
  24. package/dist/cli/tui/theme.js +414 -56
  25. package/dist/cli/tui/tool-permission.js +9 -5
  26. package/dist/cli/tui/transcript-presentation.d.ts +89 -0
  27. package/dist/cli/tui/transcript-presentation.js +169 -0
  28. package/dist/cli/tui/transcript-viewport.d.ts +5 -34
  29. package/dist/cli/tui/transcript-viewport.js +639 -158
  30. package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
  31. package/dist/cli/tui/tui-interaction-router.js +127 -0
  32. package/dist/cli/tui/tui-view-model.d.ts +6 -2
  33. package/dist/cli/tui/tui-view-model.js +23 -6
  34. package/dist/cli-runtime.d.ts +2 -2
  35. package/dist/compatibility/claude/context.d.ts +2 -2
  36. package/dist/compatibility/claude/context.js +2 -14
  37. package/dist/compatibility/claude/data-plane-adapter.d.ts +9 -0
  38. package/dist/compatibility/claude/data-plane-adapter.js +44 -0
  39. package/dist/compatibility/claude/paths.js +2 -2
  40. package/dist/compatibility/claude/shared-resources.d.ts +7 -38
  41. package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
  42. package/dist/compatibility/claude/transcript-codec.js +512 -0
  43. package/dist/core/agent-orchestration.d.ts +31 -0
  44. package/dist/core/agent-orchestration.js +235 -0
  45. package/dist/core/context.d.ts +25 -7
  46. package/dist/core/context.js +46 -0
  47. package/dist/core/prompt-composer.d.ts +8 -16
  48. package/dist/core/prompt-composer.js +65 -25
  49. package/dist/core/resources.d.ts +39 -0
  50. package/dist/core/resources.js +2 -0
  51. package/dist/core/runtime.d.ts +6 -1
  52. package/dist/core/runtime.js +15 -1
  53. package/dist/core/session.d.ts +2 -0
  54. package/dist/core/session.js +5 -0
  55. package/dist/core/transcript-codec.d.ts +70 -0
  56. package/dist/core/transcript-codec.js +60 -0
  57. package/dist/core/transcript-event.d.ts +33 -0
  58. package/dist/core/transcript-event.js +198 -0
  59. package/dist/persistence/claude-job-store.d.ts +58 -0
  60. package/dist/persistence/claude-job-store.js +483 -1
  61. package/dist/persistence/data-plane-adapter.d.ts +32 -0
  62. package/dist/persistence/data-plane-adapter.js +2 -0
  63. package/dist/persistence/data-plane.d.ts +4 -20
  64. package/dist/persistence/data-plane.js +10 -47
  65. package/dist/persistence/native-data-plane-adapter.d.ts +9 -0
  66. package/dist/persistence/native-data-plane-adapter.js +39 -0
  67. package/dist/persistence/native-resources.d.ts +4 -4
  68. package/dist/persistence/native-transcript-codec.d.ts +13 -0
  69. package/dist/persistence/native-transcript-codec.js +102 -0
  70. package/dist/persistence/native-transcript-reader.d.ts +53 -0
  71. package/dist/persistence/native-transcript-reader.js +283 -0
  72. package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
  73. package/dist/persistence/subagent-lifecycle-store.js +373 -146
  74. package/dist/platform/exclusive-file-lease.d.ts +3 -0
  75. package/dist/platform/exclusive-file-lease.js +3 -0
  76. package/dist/tools/claude-user-message.d.ts +0 -1
  77. package/dist/tools/claude-user-message.js +0 -1
  78. package/package.json +6 -2
@@ -0,0 +1,235 @@
1
+ /** Provider- and persistence-independent lifecycle contract for one Agent. */
2
+ export const TERMINAL_LIFECYCLE_STATES = [
3
+ 'completed',
4
+ 'failed',
5
+ 'cancelled',
6
+ 'orphaned',
7
+ ];
8
+ const transitions = {
9
+ queued: ['running', 'cancelling', 'failed'],
10
+ running: ['waiting', 'completed', 'failed', 'cancelling'],
11
+ waiting: ['running', 'completed', 'failed', 'cancelling'],
12
+ cancelling: ['cancelled'],
13
+ completed: [],
14
+ failed: [],
15
+ cancelled: [],
16
+ orphaned: [],
17
+ };
18
+ export function isTerminalLifecycleState(state) {
19
+ return TERMINAL_LIFECYCLE_STATES.includes(state);
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
23
+ }
24
+ function parseOwner(value) {
25
+ if (!isRecord(value))
26
+ throw new Error('Invalid lifecycle owner');
27
+ const owner = value;
28
+ const token = owner.token;
29
+ const pid = owner.pid;
30
+ const acquiredAt = owner.acquiredAt;
31
+ if (typeof token !== 'string' ||
32
+ token.trim().length === 0 ||
33
+ !Number.isSafeInteger(pid) ||
34
+ Number(pid) <= 0 ||
35
+ typeof acquiredAt !== 'string' ||
36
+ Number.isNaN(Date.parse(acquiredAt))) {
37
+ throw new Error('Invalid lifecycle owner');
38
+ }
39
+ return Object.freeze({
40
+ token: token,
41
+ pid: pid,
42
+ acquiredAt: acquiredAt,
43
+ });
44
+ }
45
+ /** The single invariant parser used by every lifecycle mutation and store. */
46
+ export function parseAgentLifecycleSnapshot(value) {
47
+ if (!isRecord(value))
48
+ throw new Error('Invalid lifecycle snapshot');
49
+ const generation = value.generation;
50
+ const revision = value.revision;
51
+ const state = value.state;
52
+ const previousOwnerToken = value.previousOwnerToken;
53
+ const acceptance = value.acceptance;
54
+ const terminalAt = value.terminalAt;
55
+ if (!Number.isSafeInteger(generation) || Number(generation) <= 0) {
56
+ throw new Error('Lifecycle generation must be a positive integer');
57
+ }
58
+ if (!Number.isSafeInteger(revision) || Number(revision) < 0) {
59
+ throw new Error('Lifecycle revision must be a nonnegative integer');
60
+ }
61
+ if (![
62
+ 'queued',
63
+ 'running',
64
+ 'waiting',
65
+ 'completed',
66
+ 'failed',
67
+ 'cancelling',
68
+ 'cancelled',
69
+ 'orphaned',
70
+ ].includes(String(state))) {
71
+ throw new Error(`Invalid lifecycle state: ${String(value.state)}`);
72
+ }
73
+ if (previousOwnerToken !== null &&
74
+ (typeof previousOwnerToken !== 'string' ||
75
+ previousOwnerToken.trim().length === 0)) {
76
+ throw new Error('Invalid previous lifecycle owner token');
77
+ }
78
+ if (!['pending', 'accepted', 'rejected'].includes(String(acceptance))) {
79
+ throw new Error('Invalid lifecycle acceptance');
80
+ }
81
+ if (isTerminalLifecycleState(state)) {
82
+ if (value.owner !== null || terminalAt === null) {
83
+ throw new Error('Terminal lifecycle must be ownerless and timestamped');
84
+ }
85
+ if (typeof terminalAt !== 'string' ||
86
+ Number.isNaN(Date.parse(terminalAt))) {
87
+ throw new Error('Invalid lifecycle terminal timestamp');
88
+ }
89
+ if (acceptance !== 'pending' && state !== 'completed') {
90
+ throw new Error('Only completed lifecycle may be accepted or rejected');
91
+ }
92
+ }
93
+ else if (value.owner === null ||
94
+ terminalAt !== null ||
95
+ acceptance !== 'pending') {
96
+ throw new Error('Nonterminal lifecycle must be owned, pending, and untimestamped');
97
+ }
98
+ const owner = value.owner === null ? null : parseOwner(value.owner);
99
+ return Object.freeze({
100
+ generation: generation,
101
+ revision: revision,
102
+ state: state,
103
+ owner,
104
+ previousOwnerToken: previousOwnerToken,
105
+ terminalAt: terminalAt,
106
+ acceptance: acceptance,
107
+ });
108
+ }
109
+ export function createAgentLifecycle(owner, generation = 1) {
110
+ parseOwner(owner);
111
+ return parseAgentLifecycleSnapshot({
112
+ generation,
113
+ revision: 0,
114
+ state: 'queued',
115
+ owner,
116
+ previousOwnerToken: null,
117
+ terminalAt: null,
118
+ acceptance: 'pending',
119
+ });
120
+ }
121
+ function requireCurrentOwner(current, token) {
122
+ if (current.owner === null || current.owner.token !== token) {
123
+ throw new Error('Lifecycle execution owner token is stale or missing');
124
+ }
125
+ }
126
+ export function transitionLifecycle(current, nextState, ownerToken, terminalAt = new Date().toISOString()) {
127
+ parseAgentLifecycleSnapshot(current);
128
+ requireCurrentOwner(current, ownerToken);
129
+ if (!transitions[current.state].includes(nextState)) {
130
+ throw new Error(`Illegal lifecycle transition: ${current.state} -> ${nextState}`);
131
+ }
132
+ const terminal = isTerminalLifecycleState(nextState);
133
+ if (terminal && Number.isNaN(Date.parse(terminalAt))) {
134
+ throw new Error('Invalid lifecycle terminal timestamp');
135
+ }
136
+ return parseAgentLifecycleSnapshot({
137
+ generation: current.generation,
138
+ revision: current.revision + 1,
139
+ state: nextState,
140
+ owner: terminal ? null : current.owner,
141
+ previousOwnerToken: terminal
142
+ ? (current.owner?.token ?? null)
143
+ : current.previousOwnerToken,
144
+ terminalAt: terminal ? terminalAt : null,
145
+ acceptance: 'pending',
146
+ });
147
+ }
148
+ function requireFreshOwner(current, owner) {
149
+ parseOwner(owner);
150
+ if (owner.token === current.previousOwnerToken) {
151
+ throw new Error('Lifecycle generation requires a fresh owner token');
152
+ }
153
+ }
154
+ /** Rotate a queued execution owner during a planned parent-to-worker handoff. */
155
+ export function transferLifecycleOwner(current, currentOwnerToken, nextOwner) {
156
+ parseAgentLifecycleSnapshot(current);
157
+ requireCurrentOwner(current, currentOwnerToken);
158
+ if (current.state !== 'queued') {
159
+ throw new Error(`Cannot transfer lifecycle owner from ${current.state}`);
160
+ }
161
+ parseOwner(nextOwner);
162
+ if (nextOwner.token === currentOwnerToken) {
163
+ throw new Error('Lifecycle handoff requires a different owner token');
164
+ }
165
+ requireFreshOwner(current, nextOwner);
166
+ return parseAgentLifecycleSnapshot({
167
+ ...current,
168
+ revision: current.revision + 1,
169
+ owner: nextOwner,
170
+ previousOwnerToken: currentOwnerToken,
171
+ });
172
+ }
173
+ export function continueLifecycle(current, owner) {
174
+ parseAgentLifecycleSnapshot(current);
175
+ if (!['completed', 'failed', 'cancelled'].includes(current.state)) {
176
+ throw new Error(`Cannot continue lifecycle from ${current.state}`);
177
+ }
178
+ requireFreshOwner(current, owner);
179
+ return parseAgentLifecycleSnapshot({
180
+ generation: current.generation + 1,
181
+ revision: current.revision + 1,
182
+ state: 'queued',
183
+ owner,
184
+ previousOwnerToken: current.previousOwnerToken,
185
+ terminalAt: null,
186
+ acceptance: 'pending',
187
+ });
188
+ }
189
+ export function recoverLifecycle(current, owner) {
190
+ parseAgentLifecycleSnapshot(current);
191
+ if (current.state !== 'orphaned') {
192
+ throw new Error(`Cannot recover lifecycle from ${current.state}`);
193
+ }
194
+ requireFreshOwner(current, owner);
195
+ return parseAgentLifecycleSnapshot({
196
+ generation: current.generation + 1,
197
+ revision: current.revision + 1,
198
+ state: 'queued',
199
+ owner,
200
+ previousOwnerToken: current.previousOwnerToken,
201
+ terminalAt: null,
202
+ acceptance: 'pending',
203
+ });
204
+ }
205
+ /** Adapter-proven owner loss is intentionally separate from ordinary transitions. */
206
+ export function markLifecycleOrphaned(current, ownerToken, terminalAt = new Date().toISOString()) {
207
+ parseAgentLifecycleSnapshot(current);
208
+ requireCurrentOwner(current, ownerToken);
209
+ if (!['queued', 'running', 'waiting', 'cancelling'].includes(current.state)) {
210
+ throw new Error(`Cannot orphan lifecycle from ${current.state}`);
211
+ }
212
+ if (Number.isNaN(Date.parse(terminalAt)))
213
+ throw new Error('Invalid lifecycle terminal timestamp');
214
+ return parseAgentLifecycleSnapshot({
215
+ generation: current.generation,
216
+ revision: current.revision + 1,
217
+ state: 'orphaned',
218
+ owner: null,
219
+ previousOwnerToken: ownerToken,
220
+ terminalAt,
221
+ acceptance: 'pending',
222
+ });
223
+ }
224
+ export function acceptLifecycle(current, generation = current.generation, acceptance = 'accepted') {
225
+ parseAgentLifecycleSnapshot(current);
226
+ if (current.state !== 'completed' || generation !== current.generation) {
227
+ throw new Error('Only the completed current generation may be accepted');
228
+ }
229
+ return parseAgentLifecycleSnapshot({
230
+ ...current,
231
+ revision: current.revision + 1,
232
+ acceptance,
233
+ });
234
+ }
235
+ //# sourceMappingURL=agent-orchestration.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
@@ -1,3 +1,4 @@
1
+ import { createContextSnapshot, } from './context.js';
1
2
  const PRODUCT_POLICY = `# Praxis
2
3
 
3
4
  You are Praxis, a local CLI coding agent. Work until the user's request is genuinely handled or a concrete blocker requires their input.
@@ -9,6 +10,8 @@ You are Praxis, a local CLI coding agent. Work until the user's request is genui
9
10
  - Summarize or clear bulky intermediate results once their useful facts have been retained.
10
11
  - Treat the context window as bounded: preserve decisions and evidence before reducing detail.
11
12
  - Keep responses concise and use the user's language unless they request otherwise.`;
13
+ const BRIEF_OUTPUT_POLICY = 'When brief mode is enabled, SendUserMessage is the primary user-visible reply channel. Use it for the answer, progress checkpoints, and blockers. Set status to normal for a direct reply and proactive for an unsolicited update.';
14
+ const STRUCTURED_OUTPUT_POLICY = 'You MUST call StructuredOutput exactly once at the end with a value matching the requested JSON Schema.';
12
15
  function baseSection(mode, content) {
13
16
  if (mode === 'bare')
14
17
  return undefined;
@@ -51,6 +54,52 @@ function normalizedSections(sections, group) {
51
54
  return { ...section };
52
55
  });
53
56
  }
57
+ function turnSections(turn) {
58
+ if (!turn)
59
+ return [];
60
+ return [
61
+ ...(turn.planMode?.trim()
62
+ ? [
63
+ {
64
+ id: 'plan-mode',
65
+ content: turn.planMode,
66
+ placement: 'system',
67
+ stability: 'volatile',
68
+ },
69
+ ]
70
+ : []),
71
+ ...(turn.sessionMemory?.trim()
72
+ ? [
73
+ {
74
+ id: 'session-memory',
75
+ content: turn.sessionMemory,
76
+ placement: 'system',
77
+ stability: 'volatile',
78
+ },
79
+ ]
80
+ : []),
81
+ ...(turn.briefOutput
82
+ ? [
83
+ {
84
+ id: 'brief-output',
85
+ content: BRIEF_OUTPUT_POLICY,
86
+ placement: 'system',
87
+ stability: 'volatile',
88
+ },
89
+ ]
90
+ : []),
91
+ ...(turn.structuredOutput
92
+ ? [
93
+ {
94
+ id: 'structured-output',
95
+ content: STRUCTURED_OUTPUT_POLICY,
96
+ placement: 'system',
97
+ stability: 'volatile',
98
+ },
99
+ ]
100
+ : []),
101
+ ];
102
+ }
54
103
  export class PromptComposer {
55
104
  compose(options) {
56
105
  const base = baseSection(options.mode, options.baseSystemPrompt);
@@ -63,37 +112,28 @@ export class PromptComposer {
63
112
  stability: 'session',
64
113
  }
65
114
  : undefined;
66
- const tailSections = normalizedSections(options.tailSections ?? [], 'tailSections');
115
+ const tailSections = normalizedSections([...(options.tailSections ?? []), ...turnSections(options.turn)], 'tailSections');
67
116
  const sections = [
68
117
  ...(base ? [base] : []),
69
118
  ...sessionSections,
70
119
  ...(append ? [append] : []),
71
120
  ...tailSections,
72
121
  ];
73
- const identities = new Set();
74
- for (const section of sections) {
75
- if (identities.has(section.id)) {
76
- throw new Error(`Duplicate prompt section ${section.id}`);
77
- }
78
- identities.add(section.id);
79
- }
80
- const systemSections = sections.filter((section) => section.placement === 'system');
81
- const firstVolatileSystemIndex = systemSections.findIndex((section) => section.stability === 'volatile');
82
- const firstUserMessageContext = sections
83
- .filter((section) => section.placement === 'first-user')
84
- .map((section) => section.content)
85
- .join('\n\n');
86
- return {
87
- sections,
88
- systemMessages: systemSections.map((section) => ({
89
- role: 'system',
90
- content: section.content,
91
- })),
92
- stableSystemSectionCount: firstVolatileSystemIndex < 0
93
- ? systemSections.length
94
- : firstVolatileSystemIndex,
95
- ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
96
- };
122
+ return createContextSnapshot(sections);
97
123
  }
98
124
  }
125
+ const defaultContextAssembler = {
126
+ async assemble(options = {}) {
127
+ return new PromptComposer().compose({
128
+ mode: options.mode ?? 'bare',
129
+ ...(options.baseSystemPrompt === undefined
130
+ ? {}
131
+ : { baseSystemPrompt: options.baseSystemPrompt }),
132
+ ...(options.turn === undefined ? {} : { turn: options.turn }),
133
+ });
134
+ },
135
+ };
136
+ export async function assembleContextSnapshot(assembler, options = {}) {
137
+ return (assembler ?? defaultContextAssembler).assemble(options);
138
+ }
99
139
  //# sourceMappingURL=prompt-composer.js.map
@@ -0,0 +1,39 @@
1
+ export type ResourceScope = 'local' | 'project' | 'user';
2
+ export interface TextResource {
3
+ path: string;
4
+ scope: ResourceScope;
5
+ content: string;
6
+ importedFrom?: string;
7
+ importRoot?: string;
8
+ }
9
+ export interface JsonResource {
10
+ path: string;
11
+ scope: ResourceScope;
12
+ value: unknown;
13
+ plugin?: true;
14
+ pluginName?: string;
15
+ pluginSource?: string;
16
+ environment?: Readonly<Record<string, string>>;
17
+ sensitiveValues?: readonly string[];
18
+ }
19
+ export interface ContextResources {
20
+ instructions: TextResource[];
21
+ conditionalRules: ConditionalRule[];
22
+ memoryIndex: TextResource | null;
23
+ }
24
+ export interface ConditionalRule extends TextResource {
25
+ baseDirectory: string;
26
+ content: string;
27
+ globs: string[];
28
+ rawContent: string;
29
+ }
30
+ export interface SharedResources {
31
+ instructions: TextResource[];
32
+ memory: TextResource[];
33
+ skills: TextResource[];
34
+ commands: TextResource[];
35
+ agents: TextResource[];
36
+ settings: JsonResource[];
37
+ mcp: JsonResource[];
38
+ }
39
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resources.js.map
@@ -151,7 +151,7 @@ export interface ModelProvider {
151
151
  }
152
152
  export type RuntimeEvent = {
153
153
  type: 'state';
154
- state: Exclude<RuntimeState, 'idle' | 'failed'>;
154
+ state: Exclude<RuntimeState, 'idle'>;
155
155
  } | {
156
156
  type: 'text-delta';
157
157
  delta: string;
@@ -419,6 +419,11 @@ export declare function permissionDecisionSource(decision: PermissionDecision):
419
419
  export declare function annotateAutoModePermissionOutcome<T extends PermissionDecision>(decision: T, outcome: AutoModePermissionOutcome): T;
420
420
  export declare function autoModePermissionOutcome(decision: PermissionDecision): AutoModePermissionOutcome | undefined;
421
421
  export interface AgentRunObserver {
422
+ modelRequestCompleted?(input: {
423
+ usage: ModelUsage;
424
+ messages: readonly ModelMessage[];
425
+ tools: readonly ModelToolDefinition[];
426
+ }): Promise<void>;
422
427
  assistantCompleted(message: {
423
428
  content: string;
424
429
  thinkingBlocks?: readonly ModelThinkingBlock[];
@@ -368,6 +368,7 @@ export class AgentRuntime {
368
368
  let textBytes = 0;
369
369
  const thinkingBlocks = [];
370
370
  let turnUsage = emptyUsage();
371
+ let turnUsageSeen = false;
371
372
  let streaming = false;
372
373
  const toolCalls = [];
373
374
  let terminalReason;
@@ -477,9 +478,10 @@ export class AgentRuntime {
477
478
  terminalReason = event.reason;
478
479
  this.emit(event);
479
480
  }
480
- else {
481
+ else if (event.type === 'usage') {
481
482
  activeAttemptHasPresentation = true;
482
483
  turnUsage = event.usage;
484
+ turnUsageSeen = true;
483
485
  this.emit(event);
484
486
  }
485
487
  }
@@ -528,6 +530,18 @@ export class AgentRuntime {
528
530
  toolCalls.length > 0) {
529
531
  return failScheduledTurn(new ModelProviderError(`Provider reported ${terminalReason} with completed tool calls`, { retryable: false }));
530
532
  }
533
+ if (turnUsageSeen) {
534
+ try {
535
+ await request.observer?.modelRequestCompleted?.({
536
+ usage: turnUsage,
537
+ messages: providerRequest.messages,
538
+ tools: definitions,
539
+ });
540
+ }
541
+ catch (error) {
542
+ return failScheduledTurn(error);
543
+ }
544
+ }
531
545
  if (request.collectMetrics) {
532
546
  const turnApiDurationWithoutRetriesMsResolved = turnApiDurationWithoutRetriesMs ?? turnApiDurationMs;
533
547
  durationApiWithoutRetriesMs = addApiDurationMetric(turnApiDurationWithoutRetriesMsResolved, durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
@@ -0,0 +1,2 @@
1
+ export declare function isSessionId(value: string): boolean;
2
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1,5 @@
1
+ const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2
+ export function isSessionId(value) {
3
+ return SESSION_ID_PATTERN.test(value);
4
+ }
5
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,70 @@
1
+ import type { TranscriptEvent } from './transcript-event.js';
2
+ export type TranscriptCodecDiagnostic = {
3
+ kind: 'unsupported-version';
4
+ lineNumber: number | null;
5
+ byteOffset: number;
6
+ message: string;
7
+ schemaVersion: string | number;
8
+ } | {
9
+ kind: 'corrupt-line';
10
+ lineNumber: number | null;
11
+ byteOffset: number;
12
+ message: string;
13
+ schemaVersion?: string | number;
14
+ } | {
15
+ kind: 'unsupported-event';
16
+ lineNumber: number | null;
17
+ byteOffset: number;
18
+ message: string;
19
+ eventKind?: string;
20
+ };
21
+ export interface OpaquePreservationToken {
22
+ readonly __opaqueToken: unique symbol;
23
+ }
24
+ type TokenPayload = {
25
+ owner: symbol;
26
+ raw: string;
27
+ eventFingerprint: string;
28
+ };
29
+ export type PreservationTokenSource = {
30
+ owner: symbol;
31
+ raw: string;
32
+ event: TranscriptEvent;
33
+ };
34
+ export declare function createPreservationToken(owner: symbol, source: PreservationTokenSource): OpaquePreservationToken;
35
+ export declare function readPreservationToken(token: OpaquePreservationToken, owner: symbol): TokenPayload | null;
36
+ export interface DecodedTranscriptRecord {
37
+ readonly event: TranscriptEvent;
38
+ readonly preservation?: OpaquePreservationToken;
39
+ }
40
+ export type TranscriptLineResult = {
41
+ ok: true;
42
+ record: DecodedTranscriptRecord;
43
+ } | {
44
+ ok: false;
45
+ issue: TranscriptCodecDiagnostic;
46
+ };
47
+ export type TranscriptEncodeResult = {
48
+ ok: true;
49
+ line: string;
50
+ } | {
51
+ ok: false;
52
+ issue: TranscriptCodecDiagnostic;
53
+ };
54
+ export interface TranscriptDocumentResult {
55
+ readonly records: readonly DecodedTranscriptRecord[];
56
+ readonly issue: TranscriptCodecDiagnostic | null;
57
+ readonly validPrefixByteLength: number;
58
+ }
59
+ export interface TranscriptCodec {
60
+ readonly id: string;
61
+ readonly version: string | number;
62
+ readonly writeMode: 'read-only' | 'read-write';
63
+ decodeLine(line: string, lineNumber?: number, byteOffset?: number): TranscriptLineResult;
64
+ encodeLine(event: TranscriptEvent, preservation?: OpaquePreservationToken): TranscriptEncodeResult;
65
+ decodeDocument(source: string | Uint8Array): TranscriptDocumentResult;
66
+ }
67
+ export declare function diagnostic(kind: TranscriptCodecDiagnostic['kind'], message: string, byteOffset: number, lineNumber: number | null, extra?: Partial<TranscriptCodecDiagnostic>): TranscriptCodecDiagnostic;
68
+ export declare function decodeDocumentWith(codec: TranscriptCodec, source: string | Uint8Array): TranscriptDocumentResult;
69
+ export {};
70
+ //# sourceMappingURL=transcript-codec.d.ts.map