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.
- package/README.md +20 -2
- package/dist/application/background-agent-manager.d.ts +16 -6
- package/dist/application/background-agent-manager.js +139 -97
- package/dist/application/context-engine.d.ts +56 -0
- package/dist/application/context-engine.js +129 -0
- package/dist/application/durable-follow-up.d.ts +14 -0
- package/dist/application/durable-follow-up.js +52 -0
- package/dist/application/native-session-transcript.d.ts +72 -0
- package/dist/application/native-session-transcript.js +351 -0
- package/dist/application/native-sidechain-transcript.d.ts +34 -0
- package/dist/application/native-sidechain-transcript.js +156 -0
- package/dist/application/session-service.d.ts +23 -4
- package/dist/application/session-service.js +1130 -494
- package/dist/application/subagent-service.d.ts +5 -1
- package/dist/application/subagent-service.js +749 -320
- package/dist/application/team-agent-runtime.d.ts +29 -0
- package/dist/application/team-agent-runtime.js +169 -0
- package/dist/application/team-capability.d.ts +27 -0
- package/dist/application/team-capability.js +41 -0
- package/dist/application/team-lead-operations.d.ts +38 -0
- package/dist/application/team-lead-operations.js +155 -0
- package/dist/application/team-mailbox.d.ts +54 -0
- package/dist/application/team-mailbox.js +196 -0
- package/dist/application/team-manager.d.ts +113 -0
- package/dist/application/team-manager.js +681 -0
- package/dist/application/team-workspace.d.ts +29 -0
- package/dist/application/team-workspace.js +197 -0
- package/dist/application/top-level-agent-manager.d.ts +1 -0
- package/dist/application/top-level-agent-manager.js +1025 -403
- package/dist/application/transcript-projection.d.ts +35 -0
- package/dist/application/transcript-projection.js +139 -0
- package/dist/application/turn-lifecycle.d.ts +2 -0
- package/dist/application/turn-memory-coordinator.d.ts +43 -0
- package/dist/application/turn-memory-coordinator.js +98 -0
- package/dist/cli/agents-dashboard.js +10 -5
- package/dist/cli/interactive.d.ts +7 -0
- package/dist/cli/interactive.js +645 -334
- package/dist/cli/tui/claude-style.d.ts +8 -25
- package/dist/cli/tui/claude-style.js +299 -277
- package/dist/cli/tui/decision-surface-model.d.ts +71 -0
- package/dist/cli/tui/decision-surface-model.js +131 -0
- package/dist/cli/tui/decision-surface.d.ts +7 -0
- package/dist/cli/tui/decision-surface.js +41 -0
- package/dist/cli/tui/help-surface-model.d.ts +68 -0
- package/dist/cli/tui/help-surface-model.js +131 -0
- package/dist/cli/tui/mcp-panel.js +1 -2
- package/dist/cli/tui/permission-surface-model.d.ts +195 -0
- package/dist/cli/tui/permission-surface-model.js +360 -0
- package/dist/cli/tui/permission-surface.d.ts +7 -0
- package/dist/cli/tui/permission-surface.js +69 -0
- package/dist/cli/tui/presentation-environment.d.ts +32 -0
- package/dist/cli/tui/presentation-environment.js +149 -0
- package/dist/cli/tui/task-panel.js +10 -1
- package/dist/cli/tui/theme.d.ts +46 -3
- package/dist/cli/tui/theme.js +414 -56
- package/dist/cli/tui/tool-permission.js +16 -8
- package/dist/cli/tui/transcript-presentation.d.ts +19 -1
- package/dist/cli/tui/transcript-presentation.js +26 -6
- package/dist/cli/tui/transcript-viewport.d.ts +18 -0
- package/dist/cli/tui/transcript-viewport.js +517 -111
- package/dist/cli/tui/transcript-window-model.d.ts +108 -0
- package/dist/cli/tui/transcript-window-model.js +772 -0
- package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli/tui/tui-screen-model.d.ts +114 -0
- package/dist/cli/tui/tui-screen-model.js +210 -0
- package/dist/cli/tui/tui-view-model.d.ts +2 -49
- package/dist/cli/tui/tui-view-model.js +1 -68
- package/dist/cli-runtime.d.ts +4 -2
- package/dist/cli-runtime.js +397 -32
- package/dist/compatibility/claude/transcript-codec.js +6 -0
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/permission-policy.d.ts +4 -0
- package/dist/core/permission-policy.js +21 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +20 -2
- package/dist/core/team-mailbox.d.ts +54 -0
- package/dist/core/team-mailbox.js +212 -0
- package/dist/core/team-ownership.d.ts +84 -0
- package/dist/core/team-ownership.js +505 -0
- package/dist/core/tool-execution-scheduler.js +6 -1
- package/dist/core/transcript-event.d.ts +5 -1
- package/dist/core/transcript-event.js +12 -0
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- package/dist/persistence/claude-transcript-store.d.ts +2 -1
- package/dist/persistence/claude-transcript-store.js +2 -10
- package/dist/persistence/native-team-mailbox.d.ts +68 -0
- package/dist/persistence/native-team-mailbox.js +542 -0
- package/dist/persistence/native-team-store.d.ts +34 -0
- package/dist/persistence/native-team-store.js +257 -0
- package/dist/persistence/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/native-transcript-store.d.ts +69 -0
- package/dist/persistence/native-transcript-store.js +321 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/persistence/transcript-file-append.d.ts +2 -0
- package/dist/persistence/transcript-file-append.js +10 -0
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -0
- package/dist/tools/claude-capabilities.d.ts +4 -0
- package/dist/tools/claude-capabilities.js +18 -2
- package/dist/tools/team-lead-tools.d.ts +19 -0
- package/dist/tools/team-lead-tools.js +479 -0
- package/dist/tools/team-member-tools.d.ts +21 -0
- package/dist/tools/team-member-tools.js +259 -0
- package/package.json +1 -1
- package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
- 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
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -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[];
|
package/dist/core/runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|