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,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
@@ -0,0 +1,4 @@
1
+ import type { PermissionResolver } from './runtime.js';
2
+ /** Intersects a child resolver with its immutable parent permission ceiling. */
3
+ export declare function composePermissionResolvers(parent: PermissionResolver, child: PermissionResolver): PermissionResolver;
4
+ //# sourceMappingURL=permission-policy.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** Intersects a child resolver with its immutable parent permission ceiling. */
2
+ export function composePermissionResolvers(parent, child) {
3
+ return {
4
+ async resolve(call, context) {
5
+ const [parentDecision, childDecision] = await Promise.all([
6
+ parent.resolve(call, context),
7
+ child.resolve(call, context),
8
+ ]);
9
+ if (parentDecision.behavior === 'deny')
10
+ return parentDecision;
11
+ if (childDecision.behavior === 'deny')
12
+ return childDecision;
13
+ if (parentDecision.behavior === 'ask')
14
+ return parentDecision;
15
+ if (childDecision.behavior === 'ask')
16
+ return childDecision;
17
+ return parentDecision;
18
+ },
19
+ };
20
+ }
21
+ //# sourceMappingURL=permission-policy.js.map
@@ -419,6 +419,12 @@ 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
+ toolExecutionStarted?(call: ModelToolCall): Promise<void>;
423
+ modelRequestCompleted?(input: {
424
+ usage: ModelUsage;
425
+ messages: readonly ModelMessage[];
426
+ tools: readonly ModelToolDefinition[];
427
+ }): Promise<void>;
422
428
  assistantCompleted(message: {
423
429
  content: string;
424
430
  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;
@@ -470,16 +471,20 @@ export class AgentRuntime {
470
471
  }
471
472
  toolCalls.push(event.call);
472
473
  this.emit(event);
473
- toolScheduler.schedule(event.call, resolveToolSchedulingPolicy(this.options.tools, event.call));
474
+ const policy = resolveToolSchedulingPolicy(this.options.tools, event.call);
475
+ toolScheduler.schedule(event.call, request.observer?.toolExecutionStarted
476
+ ? { ...policy, startAfterAssistant: true }
477
+ : policy);
474
478
  }
475
479
  else if (event.type === 'terminal') {
476
480
  activeAttemptHasPresentation = true;
477
481
  terminalReason = event.reason;
478
482
  this.emit(event);
479
483
  }
480
- else {
484
+ else if (event.type === 'usage') {
481
485
  activeAttemptHasPresentation = true;
482
486
  turnUsage = event.usage;
487
+ turnUsageSeen = true;
483
488
  this.emit(event);
484
489
  }
485
490
  }
@@ -528,6 +533,18 @@ export class AgentRuntime {
528
533
  toolCalls.length > 0) {
529
534
  return failScheduledTurn(new ModelProviderError(`Provider reported ${terminalReason} with completed tool calls`, { retryable: false }));
530
535
  }
536
+ if (turnUsageSeen) {
537
+ try {
538
+ await request.observer?.modelRequestCompleted?.({
539
+ usage: turnUsage,
540
+ messages: providerRequest.messages,
541
+ tools: definitions,
542
+ });
543
+ }
544
+ catch (error) {
545
+ return failScheduledTurn(error);
546
+ }
547
+ }
531
548
  if (request.collectMetrics) {
532
549
  const turnApiDurationWithoutRetriesMsResolved = turnApiDurationWithoutRetriesMs ?? turnApiDurationMs;
533
550
  durationApiWithoutRetriesMs = addApiDurationMetric(turnApiDurationWithoutRetriesMsResolved, durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
@@ -942,6 +959,7 @@ export class AgentRuntime {
942
959
  this.emit({ type: 'state', state: 'executing-tools' });
943
960
  context.permissionPhase = 'execute';
944
961
  context.permissionApproved = true;
962
+ await request.observer?.toolExecutionStarted?.(prepared);
945
963
  const toolStartedAt = performance.now();
946
964
  try {
947
965
  const result = await tools.execute(prepared, context);
@@ -0,0 +1,54 @@
1
+ export type TeamMailboxPayload = {
2
+ readonly kind: 'text';
3
+ readonly text: string;
4
+ readonly summary?: string;
5
+ } | {
6
+ readonly kind: 'task';
7
+ readonly phase: 'request';
8
+ readonly requestId: string;
9
+ readonly taskId: string;
10
+ readonly text: string;
11
+ } | {
12
+ readonly kind: 'task';
13
+ readonly phase: 'response';
14
+ readonly requestId: string;
15
+ readonly taskId: string;
16
+ readonly status: 'accepted' | 'rejected' | 'completed' | 'failed';
17
+ readonly text?: string;
18
+ } | {
19
+ readonly kind: 'shutdown';
20
+ readonly phase: 'request';
21
+ readonly requestId: string;
22
+ readonly reason?: string;
23
+ } | {
24
+ readonly kind: 'shutdown';
25
+ readonly phase: 'response';
26
+ readonly requestId: string;
27
+ readonly approved: boolean;
28
+ readonly reason?: string;
29
+ } | {
30
+ readonly kind: 'plan';
31
+ readonly phase: 'request';
32
+ readonly requestId: string;
33
+ readonly plan: string;
34
+ } | {
35
+ readonly kind: 'plan';
36
+ readonly phase: 'response';
37
+ readonly requestId: string;
38
+ readonly approved: boolean;
39
+ readonly feedback?: string;
40
+ };
41
+ export interface TeamMailboxMessage {
42
+ readonly version: 1;
43
+ readonly sequence: number;
44
+ readonly messageId: string;
45
+ readonly teamId: string;
46
+ readonly sender: string;
47
+ readonly recipients: readonly string[];
48
+ readonly payload: TeamMailboxPayload;
49
+ readonly createdAt: string;
50
+ }
51
+ export declare function parseTeamMailboxMessageId(value: unknown): string;
52
+ export declare function parseTeamMailboxPayload(value: unknown): TeamMailboxPayload;
53
+ export declare function parseTeamMailboxMessage(value: unknown): TeamMailboxMessage;
54
+ //# sourceMappingURL=team-mailbox.d.ts.map
@@ -0,0 +1,212 @@
1
+ import { parseTeamId } from './team-ownership.js';
2
+ const MAX_ID_LENGTH = 300;
3
+ const MAX_PAYLOAD_BYTES = 16 * 1024;
4
+ const MAX_MESSAGE_BYTES = 64 * 1024;
5
+ const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
6
+ function object(value, label) {
7
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
8
+ throw new Error(`Invalid ${label}`);
9
+ return value;
10
+ }
11
+ function closed(value, keys) {
12
+ const allowed = new Set(keys);
13
+ for (const key of Object.keys(value))
14
+ if (!allowed.has(key))
15
+ throw new Error(`Unknown Team mailbox field: ${key}`);
16
+ }
17
+ function id(value, label) {
18
+ if (typeof value !== 'string' ||
19
+ value.trim() === '' ||
20
+ value !== value.trim() ||
21
+ value.length > MAX_ID_LENGTH ||
22
+ value.includes('\r') ||
23
+ value.includes('\n'))
24
+ throw new Error(`Invalid ${label}`);
25
+ return value;
26
+ }
27
+ export function parseTeamMailboxMessageId(value) {
28
+ return id(value, 'message ID');
29
+ }
30
+ function participantName(value, label) {
31
+ if (typeof value !== 'string' || value.trim() === '')
32
+ throw new Error(`Invalid ${label}`);
33
+ return value;
34
+ }
35
+ function body(value, label) {
36
+ if (typeof value !== 'string' || value.trim() === '')
37
+ throw new Error(`Invalid ${label}`);
38
+ return value;
39
+ }
40
+ function optionalBody(value, label) {
41
+ if (value === undefined)
42
+ return undefined;
43
+ return body(value, label);
44
+ }
45
+ function timestamp(value) {
46
+ if (typeof value !== 'string' ||
47
+ !ISO_PATTERN.test(value) ||
48
+ Number.isNaN(Date.parse(value)) ||
49
+ new Date(value).toISOString() !== value)
50
+ throw new Error('Invalid Team mailbox timestamp');
51
+ return value;
52
+ }
53
+ function freeze(value) {
54
+ if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {
55
+ for (const child of Object.values(value))
56
+ freeze(child);
57
+ Object.freeze(value);
58
+ }
59
+ return value;
60
+ }
61
+ function assertSerializedBytes(value, limit, label) {
62
+ const serialized = JSON.stringify(value);
63
+ if (serialized === undefined || Buffer.byteLength(serialized, 'utf8') > limit)
64
+ throw new Error(`${label} exceeds ${limit} UTF-8 bytes`);
65
+ }
66
+ export function parseTeamMailboxPayload(value) {
67
+ const source = object(value, 'Team mailbox payload');
68
+ if (source.kind === 'text') {
69
+ closed(source, ['kind', 'text', 'summary']);
70
+ const result = {
71
+ kind: 'text',
72
+ text: body(source.text, 'text message'),
73
+ ...(source.summary === undefined
74
+ ? {}
75
+ : { summary: body(source.summary, 'message summary') }),
76
+ };
77
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
78
+ return freeze(result);
79
+ }
80
+ if (source.kind === 'task') {
81
+ if (source.phase === 'request') {
82
+ closed(source, ['kind', 'phase', 'requestId', 'taskId', 'text']);
83
+ const result = {
84
+ kind: 'task',
85
+ phase: 'request',
86
+ requestId: id(source.requestId, 'request ID'),
87
+ taskId: id(source.taskId, 'task ID'),
88
+ text: body(source.text, 'task request text'),
89
+ };
90
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
91
+ return freeze(result);
92
+ }
93
+ if (source.phase === 'response') {
94
+ closed(source, ['kind', 'phase', 'requestId', 'taskId', 'status', 'text']);
95
+ if (source.status !== 'accepted' &&
96
+ source.status !== 'rejected' &&
97
+ source.status !== 'completed' &&
98
+ source.status !== 'failed')
99
+ throw new Error('Invalid task response status');
100
+ const text = optionalBody(source.text, 'task response text');
101
+ const result = {
102
+ kind: 'task',
103
+ phase: 'response',
104
+ requestId: id(source.requestId, 'request ID'),
105
+ taskId: id(source.taskId, 'task ID'),
106
+ status: source.status,
107
+ ...(text === undefined ? {} : { text }),
108
+ };
109
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
110
+ return freeze(result);
111
+ }
112
+ }
113
+ if (source.kind === 'shutdown') {
114
+ if (source.phase === 'request') {
115
+ closed(source, ['kind', 'phase', 'requestId', 'reason']);
116
+ const reason = optionalBody(source.reason, 'shutdown reason');
117
+ const result = {
118
+ kind: 'shutdown',
119
+ phase: 'request',
120
+ requestId: id(source.requestId, 'request ID'),
121
+ ...(reason === undefined ? {} : { reason }),
122
+ };
123
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
124
+ return freeze(result);
125
+ }
126
+ if (source.phase === 'response') {
127
+ closed(source, ['kind', 'phase', 'requestId', 'approved', 'reason']);
128
+ if (typeof source.approved !== 'boolean')
129
+ throw new Error('Invalid shutdown approval');
130
+ const reason = optionalBody(source.reason, 'shutdown reason');
131
+ const result = {
132
+ kind: 'shutdown',
133
+ phase: 'response',
134
+ requestId: id(source.requestId, 'request ID'),
135
+ approved: source.approved,
136
+ ...(reason === undefined ? {} : { reason }),
137
+ };
138
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
139
+ return freeze(result);
140
+ }
141
+ }
142
+ if (source.kind === 'plan') {
143
+ if (source.phase === 'request') {
144
+ closed(source, ['kind', 'phase', 'requestId', 'plan']);
145
+ const result = {
146
+ kind: 'plan',
147
+ phase: 'request',
148
+ requestId: id(source.requestId, 'request ID'),
149
+ plan: body(source.plan, 'plan'),
150
+ };
151
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
152
+ return freeze(result);
153
+ }
154
+ if (source.phase === 'response') {
155
+ closed(source, ['kind', 'phase', 'requestId', 'approved', 'feedback']);
156
+ if (typeof source.approved !== 'boolean')
157
+ throw new Error('Invalid plan approval');
158
+ const feedback = optionalBody(source.feedback, 'plan feedback');
159
+ const result = {
160
+ kind: 'plan',
161
+ phase: 'response',
162
+ requestId: id(source.requestId, 'request ID'),
163
+ approved: source.approved,
164
+ ...(feedback === undefined ? {} : { feedback }),
165
+ };
166
+ assertSerializedBytes(result, MAX_PAYLOAD_BYTES, 'Team mailbox payload');
167
+ return freeze(result);
168
+ }
169
+ }
170
+ throw new Error('Invalid Team mailbox payload variant');
171
+ }
172
+ export function parseTeamMailboxMessage(value) {
173
+ const source = object(value, 'Team mailbox message');
174
+ closed(source, [
175
+ 'version',
176
+ 'sequence',
177
+ 'messageId',
178
+ 'teamId',
179
+ 'sender',
180
+ 'recipients',
181
+ 'payload',
182
+ 'createdAt',
183
+ ]);
184
+ if (source.version !== 1)
185
+ throw new Error('Invalid Team mailbox version');
186
+ if (!Number.isSafeInteger(source.sequence) ||
187
+ source.sequence <= 0)
188
+ throw new Error('Invalid Team mailbox sequence');
189
+ const messageId = id(source.messageId, 'message ID');
190
+ const teamId = parseTeamId(source.teamId);
191
+ const sender = participantName(source.sender, 'sender');
192
+ if (!Array.isArray(source.recipients) || source.recipients.length === 0)
193
+ throw new Error('Invalid Team mailbox recipients');
194
+ const recipients = source.recipients.map((entry) => participantName(entry, 'recipient'));
195
+ if (new Set(recipients).size !== recipients.length)
196
+ throw new Error('Duplicate Team mailbox recipient');
197
+ const payload = parseTeamMailboxPayload(source.payload);
198
+ const createdAt = timestamp(source.createdAt);
199
+ const result = {
200
+ version: 1,
201
+ sequence: source.sequence,
202
+ messageId,
203
+ teamId,
204
+ sender,
205
+ recipients: freeze(recipients),
206
+ payload,
207
+ createdAt,
208
+ };
209
+ assertSerializedBytes(result, MAX_MESSAGE_BYTES, 'Team mailbox message');
210
+ return freeze(result);
211
+ }
212
+ //# sourceMappingURL=team-mailbox.js.map
@@ -0,0 +1,84 @@
1
+ import { type AgentLifecycleSnapshot } from './agent-orchestration.js';
2
+ export type TeamMemberAccess = 'read-only' | 'write';
3
+ export type TeamLeadPolicy = 'hybrid' | 'coordinator';
4
+ export type TeamExecutionPolicy = 'sequential' | 'swarm';
5
+ export type TeamCommitPolicy = 'lead';
6
+ export interface TeamBudgets {
7
+ readonly maxAgents: number;
8
+ readonly maxConcurrent: number;
9
+ readonly maxTokens: number;
10
+ readonly maxDurationMs: number;
11
+ readonly shutdownDrainMs: number;
12
+ }
13
+ export interface TeamUsage {
14
+ readonly totalTokens: number;
15
+ readonly durationMs: number;
16
+ readonly exhausted: null | {
17
+ readonly reason: 'tokens' | 'duration';
18
+ readonly at: string;
19
+ };
20
+ }
21
+ export declare const DEFAULT_TEAM_BUDGETS: TeamBudgets;
22
+ export interface TeamMember {
23
+ readonly name: string;
24
+ readonly agentType: string;
25
+ readonly access: TeamMemberAccess;
26
+ }
27
+ export interface TeamTaskClaims {
28
+ readonly files: readonly string[];
29
+ readonly publicContracts: readonly string[];
30
+ readonly generatedArtifacts: readonly string[];
31
+ readonly migrations: readonly string[];
32
+ readonly mergeTargets: readonly string[];
33
+ }
34
+ export interface TeamTask {
35
+ readonly id: string;
36
+ readonly description: string;
37
+ readonly assignee: string;
38
+ readonly blockedBy: readonly string[];
39
+ readonly claims: TeamTaskClaims;
40
+ readonly execution: AgentLifecycleSnapshot | null;
41
+ readonly usage: {
42
+ readonly generation: number;
43
+ readonly totalTokens: number;
44
+ readonly durationMs: number;
45
+ };
46
+ }
47
+ export interface TeamSnapshot {
48
+ readonly version: 2;
49
+ readonly revision: number;
50
+ readonly teamId: string;
51
+ readonly name: string;
52
+ readonly projectIdentity: string;
53
+ readonly leadSessionId: string;
54
+ readonly roster: readonly TeamMember[];
55
+ readonly tasks: readonly TeamTask[];
56
+ readonly createdAt: string;
57
+ readonly updatedAt: string;
58
+ readonly policy: {
59
+ readonly lead: TeamLeadPolicy;
60
+ readonly execution: TeamExecutionPolicy;
61
+ readonly commit: TeamCommitPolicy;
62
+ };
63
+ readonly budgets: TeamBudgets;
64
+ readonly usage: TeamUsage;
65
+ }
66
+ export interface TeamAdmissionInput {
67
+ readonly snapshot: TeamSnapshot;
68
+ readonly maxConcurrent: number;
69
+ }
70
+ export declare function parseTeamId(value: unknown): string;
71
+ export declare function parseTeamBudgetOverrides(value: unknown): Readonly<Partial<TeamBudgets>>;
72
+ export declare function parseTeamSnapshot(value: unknown): TeamSnapshot;
73
+ export declare function teamTasksConflict(left: TeamTask, right: TeamTask): boolean;
74
+ export declare function selectTeamTaskAdmissions(input: TeamAdmissionInput): readonly string[];
75
+ export declare function withTeamTaskExecution(snapshot: TeamSnapshot, taskId: string, execution: AgentLifecycleSnapshot): TeamSnapshot;
76
+ export declare function acceptTeamTaskExecution(snapshot: TeamSnapshot, taskId: string, generation?: number, acceptance?: 'accepted' | 'rejected'): TeamSnapshot;
77
+ export declare function recordTeamTaskProgress(snapshot: TeamSnapshot, taskId: string, progress: {
78
+ generation: number;
79
+ totalTokens: number;
80
+ durationMs: number;
81
+ }, exhaustedAt?: string): TeamSnapshot;
82
+ export declare function updateTeamUsageDuration(snapshot: TeamSnapshot, durationMs: number, exhaustedAt?: string): TeamSnapshot;
83
+ export declare function markTeamBudgetExhausted(snapshot: TeamSnapshot, reason: 'tokens' | 'duration', at?: string): TeamSnapshot;
84
+ //# sourceMappingURL=team-ownership.d.ts.map