praxis-agent 0.36.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 +16 -2
- 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 +15 -0
- package/dist/application/session-service.js +611 -69
- package/dist/application/subagent-service.d.ts +5 -1
- package/dist/application/subagent-service.js +391 -143
- 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/transcript-projection.d.ts +2 -0
- package/dist/application/transcript-projection.js +30 -1
- package/dist/cli/interactive.d.ts +7 -0
- package/dist/cli/interactive.js +387 -136
- package/dist/cli/tui/claude-style.d.ts +8 -25
- package/dist/cli/tui/claude-style.js +158 -160
- 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/tool-permission.js +7 -3
- 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-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 +2 -0
- package/dist/cli-runtime.js +397 -32
- package/dist/compatibility/claude/transcript-codec.js +6 -0
- package/dist/core/permission-policy.d.ts +4 -0
- package/dist/core/permission-policy.js +21 -0
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +5 -1
- 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-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-store.d.ts +69 -0
- package/dist/persistence/native-transcript-store.js +321 -0
- package/dist/persistence/transcript-file-append.d.ts +2 -0
- package/dist/persistence/transcript-file-append.js +10 -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,505 @@
|
|
|
1
|
+
import { acceptLifecycle, parseAgentLifecycleSnapshot, } from './agent-orchestration.js';
|
|
2
|
+
export const DEFAULT_TEAM_BUDGETS = Object.freeze({
|
|
3
|
+
maxAgents: 4,
|
|
4
|
+
maxConcurrent: 4,
|
|
5
|
+
maxTokens: 500000,
|
|
6
|
+
maxDurationMs: 3600000,
|
|
7
|
+
shutdownDrainMs: 5000,
|
|
8
|
+
});
|
|
9
|
+
const lifecycleActive = new Set(['queued', 'running', 'waiting', 'cancelling']);
|
|
10
|
+
const TEAM_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
|
|
11
|
+
const claimKinds = [
|
|
12
|
+
'files',
|
|
13
|
+
'publicContracts',
|
|
14
|
+
'generatedArtifacts',
|
|
15
|
+
'migrations',
|
|
16
|
+
'mergeTargets',
|
|
17
|
+
];
|
|
18
|
+
function record(value) {
|
|
19
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
20
|
+
throw new Error('Invalid Team value');
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function closed(value, keys) {
|
|
24
|
+
const allowed = new Set(keys);
|
|
25
|
+
for (const key of Object.keys(value))
|
|
26
|
+
if (!allowed.has(key))
|
|
27
|
+
throw new Error(`Unknown Team field: ${key}`);
|
|
28
|
+
}
|
|
29
|
+
function nonblank(value, label) {
|
|
30
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
31
|
+
throw new Error(`Invalid ${label}`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
export function parseTeamId(value) {
|
|
35
|
+
if (typeof value !== 'string' || !TEAM_ID_PATTERN.test(value))
|
|
36
|
+
throw new Error(`Invalid Team ID: ${String(value)}`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function iso(value, label) {
|
|
40
|
+
if (typeof value !== 'string' ||
|
|
41
|
+
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ||
|
|
42
|
+
Number.isNaN(Date.parse(value)) ||
|
|
43
|
+
new Date(value).toISOString() !== value)
|
|
44
|
+
throw new Error(`Invalid ${label} timestamp`);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function freeze(value) {
|
|
48
|
+
if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {
|
|
49
|
+
for (const child of Object.values(value))
|
|
50
|
+
freeze(child);
|
|
51
|
+
Object.freeze(value);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function claim(value) {
|
|
56
|
+
const result = nonblank(value, 'claim');
|
|
57
|
+
if (result !== result.trim() ||
|
|
58
|
+
result.includes('\\') ||
|
|
59
|
+
result.includes('\0') ||
|
|
60
|
+
result.startsWith('/') ||
|
|
61
|
+
/^[A-Za-z]:\//.test(result))
|
|
62
|
+
throw new Error('Invalid repository-relative claim');
|
|
63
|
+
const segments = result.split('/');
|
|
64
|
+
if (segments.some((segment) => segment === '' || segment === '.' || segment === '..'))
|
|
65
|
+
throw new Error('Invalid repository-relative claim');
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function nextTeamTimestamp(current) {
|
|
69
|
+
return new Date(Math.max(Date.now(), Date.parse(current.updatedAt))).toISOString();
|
|
70
|
+
}
|
|
71
|
+
function budget(value, key, max) {
|
|
72
|
+
if (!Number.isSafeInteger(value) ||
|
|
73
|
+
value < (key === 'shutdownDrainMs' ? 0 : 1) ||
|
|
74
|
+
(max !== undefined && value > max))
|
|
75
|
+
throw new Error(`Invalid Team budget: ${key}`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
export function parseTeamBudgetOverrides(value) {
|
|
79
|
+
const source = record(value);
|
|
80
|
+
closed(source, [
|
|
81
|
+
'maxAgents',
|
|
82
|
+
'maxConcurrent',
|
|
83
|
+
'maxTokens',
|
|
84
|
+
'maxDurationMs',
|
|
85
|
+
'shutdownDrainMs',
|
|
86
|
+
]);
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const key of Object.keys(source))
|
|
89
|
+
result[key] = budget(source[key], key, key === 'shutdownDrainMs' ? 600000 : undefined);
|
|
90
|
+
return freeze(result);
|
|
91
|
+
}
|
|
92
|
+
function parseBudgets(value) {
|
|
93
|
+
const result = parseTeamBudgetOverrides(value);
|
|
94
|
+
for (const key of [
|
|
95
|
+
'maxAgents',
|
|
96
|
+
'maxConcurrent',
|
|
97
|
+
'maxTokens',
|
|
98
|
+
'maxDurationMs',
|
|
99
|
+
'shutdownDrainMs',
|
|
100
|
+
])
|
|
101
|
+
if (result[key] === undefined)
|
|
102
|
+
throw new Error(`Invalid Team budget: ${key}`);
|
|
103
|
+
const complete = result;
|
|
104
|
+
if (complete.maxConcurrent > complete.maxAgents)
|
|
105
|
+
throw new Error('Invalid Team budget: maxConcurrent');
|
|
106
|
+
return freeze(complete);
|
|
107
|
+
}
|
|
108
|
+
function parseUsage(value) {
|
|
109
|
+
const source = record(value);
|
|
110
|
+
closed(source, ['totalTokens', 'durationMs', 'exhausted']);
|
|
111
|
+
if (!Number.isSafeInteger(source.totalTokens) ||
|
|
112
|
+
source.totalTokens < 0 ||
|
|
113
|
+
!Number.isSafeInteger(source.durationMs) ||
|
|
114
|
+
source.durationMs < 0)
|
|
115
|
+
throw new Error('Invalid Team usage');
|
|
116
|
+
let exhausted = null;
|
|
117
|
+
if (source.exhausted !== null) {
|
|
118
|
+
const entry = record(source.exhausted);
|
|
119
|
+
closed(entry, ['reason', 'at']);
|
|
120
|
+
if (entry.reason !== 'tokens' && entry.reason !== 'duration')
|
|
121
|
+
throw new Error('Invalid Team exhaustion reason');
|
|
122
|
+
exhausted = Object.freeze({
|
|
123
|
+
reason: entry.reason,
|
|
124
|
+
at: iso(entry.at, 'exhaustion'),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return freeze({
|
|
128
|
+
totalTokens: source.totalTokens,
|
|
129
|
+
durationMs: source.durationMs,
|
|
130
|
+
exhausted,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function claims(value) {
|
|
134
|
+
const source = record(value);
|
|
135
|
+
closed(source, claimKinds);
|
|
136
|
+
const result = {};
|
|
137
|
+
for (const kind of claimKinds) {
|
|
138
|
+
if (!Array.isArray(source[kind]))
|
|
139
|
+
throw new Error(`Invalid ${kind} claims`);
|
|
140
|
+
const values = source[kind].map(claim);
|
|
141
|
+
if (new Set(values).size !== values.length)
|
|
142
|
+
throw new Error('Duplicate claim');
|
|
143
|
+
result[kind] = values;
|
|
144
|
+
}
|
|
145
|
+
return freeze(result);
|
|
146
|
+
}
|
|
147
|
+
export function parseTeamSnapshot(value) {
|
|
148
|
+
const source = record(value);
|
|
149
|
+
closed(source, [
|
|
150
|
+
'version',
|
|
151
|
+
'revision',
|
|
152
|
+
'teamId',
|
|
153
|
+
'name',
|
|
154
|
+
'projectIdentity',
|
|
155
|
+
'leadSessionId',
|
|
156
|
+
'roster',
|
|
157
|
+
'tasks',
|
|
158
|
+
'createdAt',
|
|
159
|
+
'updatedAt',
|
|
160
|
+
'policy',
|
|
161
|
+
'budgets',
|
|
162
|
+
'usage',
|
|
163
|
+
]);
|
|
164
|
+
if (source.version !== 1 && source.version !== 2)
|
|
165
|
+
throw new Error('Invalid Team version');
|
|
166
|
+
if (!Number.isSafeInteger(source.revision) || source.revision < 0)
|
|
167
|
+
throw new Error('Invalid Team revision');
|
|
168
|
+
const teamId = parseTeamId(source.teamId);
|
|
169
|
+
const name = nonblank(source.name, 'Team name');
|
|
170
|
+
const projectIdentity = nonblank(source.projectIdentity, 'project identity');
|
|
171
|
+
const leadSessionId = nonblank(source.leadSessionId, 'lead session ID');
|
|
172
|
+
const createdAt = iso(source.createdAt, 'createdAt');
|
|
173
|
+
const updatedAt = iso(source.updatedAt, 'updatedAt');
|
|
174
|
+
if (updatedAt < createdAt)
|
|
175
|
+
throw new Error('updatedAt precedes createdAt');
|
|
176
|
+
if (!Array.isArray(source.roster) || !Array.isArray(source.tasks))
|
|
177
|
+
throw new Error('Invalid Team roster or tasks');
|
|
178
|
+
const roster = source.roster.map((entry) => {
|
|
179
|
+
const member = record(entry);
|
|
180
|
+
closed(member, ['name', 'agentType', 'access']);
|
|
181
|
+
const result = {
|
|
182
|
+
name: nonblank(member.name, 'member name'),
|
|
183
|
+
agentType: nonblank(member.agentType, 'agent type'),
|
|
184
|
+
access: member.access,
|
|
185
|
+
};
|
|
186
|
+
if (result.access !== 'read-only' && result.access !== 'write')
|
|
187
|
+
throw new Error('Invalid member access');
|
|
188
|
+
return freeze(result);
|
|
189
|
+
});
|
|
190
|
+
const names = new Set(roster.map((member) => member.name));
|
|
191
|
+
if (names.size !== roster.length || names.has('lead'))
|
|
192
|
+
throw new Error('Invalid or reserved roster member name');
|
|
193
|
+
const tasks = source.tasks.map((entry) => {
|
|
194
|
+
const task = record(entry);
|
|
195
|
+
closed(task, [
|
|
196
|
+
'id',
|
|
197
|
+
'description',
|
|
198
|
+
'assignee',
|
|
199
|
+
'blockedBy',
|
|
200
|
+
'claims',
|
|
201
|
+
'execution',
|
|
202
|
+
'usage',
|
|
203
|
+
]);
|
|
204
|
+
const id = nonblank(task.id, 'task ID');
|
|
205
|
+
const description = nonblank(task.description, 'task description');
|
|
206
|
+
const assignee = nonblank(task.assignee, 'task assignee');
|
|
207
|
+
if (!names.has(assignee))
|
|
208
|
+
throw new Error('Task assignee is not a roster member');
|
|
209
|
+
if (!Array.isArray(task.blockedBy))
|
|
210
|
+
throw new Error('Invalid task blockers');
|
|
211
|
+
const blockedBy = task.blockedBy.map((blocker) => nonblank(blocker, 'blocker ID'));
|
|
212
|
+
if (new Set(blockedBy).size !== blockedBy.length || blockedBy.includes(id))
|
|
213
|
+
throw new Error('Invalid task blockers');
|
|
214
|
+
const execution = task.execution === null
|
|
215
|
+
? null
|
|
216
|
+
: parseAgentLifecycleSnapshot(task.execution);
|
|
217
|
+
const taskUsage = source.version === 1
|
|
218
|
+
? {
|
|
219
|
+
generation: execution?.generation ?? 0,
|
|
220
|
+
totalTokens: 0,
|
|
221
|
+
durationMs: 0,
|
|
222
|
+
}
|
|
223
|
+
: (() => {
|
|
224
|
+
if (task.usage === undefined)
|
|
225
|
+
throw new Error('Missing task usage');
|
|
226
|
+
const u = record(task.usage);
|
|
227
|
+
closed(u, ['generation', 'totalTokens', 'durationMs']);
|
|
228
|
+
for (const key of [
|
|
229
|
+
'generation',
|
|
230
|
+
'totalTokens',
|
|
231
|
+
'durationMs',
|
|
232
|
+
])
|
|
233
|
+
if (!Number.isSafeInteger(u[key]) || u[key] < 0)
|
|
234
|
+
throw new Error('Invalid task usage');
|
|
235
|
+
return {
|
|
236
|
+
generation: u.generation,
|
|
237
|
+
totalTokens: u.totalTokens,
|
|
238
|
+
durationMs: u.durationMs,
|
|
239
|
+
};
|
|
240
|
+
})();
|
|
241
|
+
return freeze({
|
|
242
|
+
id,
|
|
243
|
+
description,
|
|
244
|
+
assignee,
|
|
245
|
+
blockedBy: freeze(blockedBy),
|
|
246
|
+
claims: claims(task.claims),
|
|
247
|
+
execution,
|
|
248
|
+
usage: freeze(taskUsage),
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
252
|
+
if (ids.size !== tasks.length)
|
|
253
|
+
throw new Error('Duplicate task ID');
|
|
254
|
+
for (const task of tasks)
|
|
255
|
+
for (const blocker of task.blockedBy)
|
|
256
|
+
if (!ids.has(blocker))
|
|
257
|
+
throw new Error('Unknown task blocker');
|
|
258
|
+
const visiting = new Set();
|
|
259
|
+
const visited = new Set();
|
|
260
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
261
|
+
const visit = (id) => {
|
|
262
|
+
if (visiting.has(id))
|
|
263
|
+
throw new Error('Task dependency cycle');
|
|
264
|
+
if (visited.has(id))
|
|
265
|
+
return;
|
|
266
|
+
visiting.add(id);
|
|
267
|
+
const current = byId.get(id);
|
|
268
|
+
if (!current)
|
|
269
|
+
throw new Error('Unknown task blocker');
|
|
270
|
+
for (const blocker of current.blockedBy)
|
|
271
|
+
visit(blocker);
|
|
272
|
+
visiting.delete(id);
|
|
273
|
+
visited.add(id);
|
|
274
|
+
};
|
|
275
|
+
for (const task of tasks)
|
|
276
|
+
visit(task.id);
|
|
277
|
+
for (const task of tasks) {
|
|
278
|
+
const generation = task.usage.generation;
|
|
279
|
+
if (source.version === 2 &&
|
|
280
|
+
generation === 0 &&
|
|
281
|
+
(task.execution !== null ||
|
|
282
|
+
task.usage.totalTokens !== 0 ||
|
|
283
|
+
task.usage.durationMs !== 0))
|
|
284
|
+
throw new Error('Task usage generation mismatch');
|
|
285
|
+
if (source.version === 2 &&
|
|
286
|
+
generation > 0 &&
|
|
287
|
+
(task.execution === null || task.execution.generation !== generation))
|
|
288
|
+
throw new Error('Task usage generation mismatch');
|
|
289
|
+
}
|
|
290
|
+
const policy = source.version === 1
|
|
291
|
+
? {
|
|
292
|
+
lead: 'hybrid',
|
|
293
|
+
execution: 'swarm',
|
|
294
|
+
commit: 'lead',
|
|
295
|
+
}
|
|
296
|
+
: (() => {
|
|
297
|
+
const p = record(source.policy);
|
|
298
|
+
closed(p, ['lead', 'execution', 'commit']);
|
|
299
|
+
if ((p.lead !== 'hybrid' && p.lead !== 'coordinator') ||
|
|
300
|
+
(p.execution !== 'sequential' && p.execution !== 'swarm') ||
|
|
301
|
+
p.commit !== 'lead')
|
|
302
|
+
throw new Error('Invalid Team policy');
|
|
303
|
+
return { lead: p.lead, execution: p.execution, commit: p.commit };
|
|
304
|
+
})();
|
|
305
|
+
const budgets = source.version === 1
|
|
306
|
+
? {
|
|
307
|
+
...DEFAULT_TEAM_BUDGETS,
|
|
308
|
+
maxAgents: Math.max(DEFAULT_TEAM_BUDGETS.maxAgents, roster.length),
|
|
309
|
+
}
|
|
310
|
+
: parseBudgets(source.budgets);
|
|
311
|
+
if (roster.length > budgets.maxAgents)
|
|
312
|
+
throw new Error('Team roster exceeds maxAgents');
|
|
313
|
+
const usage = source.version === 1
|
|
314
|
+
? { totalTokens: 0, durationMs: 0, exhausted: null }
|
|
315
|
+
: parseUsage(source.usage);
|
|
316
|
+
const taskTotal = tasks.reduce((sum, task) => sum + task.usage.totalTokens, 0);
|
|
317
|
+
if (usage.totalTokens !== taskTotal)
|
|
318
|
+
throw new Error('Team token usage does not match tasks');
|
|
319
|
+
return freeze({
|
|
320
|
+
version: 2,
|
|
321
|
+
revision: source.revision,
|
|
322
|
+
teamId,
|
|
323
|
+
name,
|
|
324
|
+
projectIdentity,
|
|
325
|
+
leadSessionId,
|
|
326
|
+
roster: freeze(roster),
|
|
327
|
+
tasks: freeze(tasks),
|
|
328
|
+
createdAt,
|
|
329
|
+
updatedAt,
|
|
330
|
+
policy: freeze(policy),
|
|
331
|
+
budgets,
|
|
332
|
+
usage,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function claimSets(task) {
|
|
336
|
+
return new Map(claimKinds.map((kind) => [kind, new Set(task.claims[kind])]));
|
|
337
|
+
}
|
|
338
|
+
export function teamTasksConflict(left, right) {
|
|
339
|
+
const a = claimSets(left);
|
|
340
|
+
const b = claimSets(right);
|
|
341
|
+
return claimKinds.some((kind) => {
|
|
342
|
+
const leftClaims = a.get(kind);
|
|
343
|
+
const rightClaims = b.get(kind);
|
|
344
|
+
return (leftClaims !== undefined &&
|
|
345
|
+
rightClaims !== undefined &&
|
|
346
|
+
[...leftClaims].some((value) => rightClaims.has(value)));
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
export function selectTeamTaskAdmissions(input) {
|
|
350
|
+
const snapshot = parseTeamSnapshot(input.snapshot);
|
|
351
|
+
if (!Number.isSafeInteger(input.maxConcurrent) || input.maxConcurrent <= 0)
|
|
352
|
+
throw new Error('Invalid maxConcurrent');
|
|
353
|
+
const active = snapshot.tasks.filter((task) => task.execution !== null && lifecycleActive.has(task.execution.state));
|
|
354
|
+
const effective = Math.min(input.maxConcurrent, snapshot.budgets.maxConcurrent, snapshot.budgets.maxAgents);
|
|
355
|
+
let remaining = (snapshot.policy.execution === 'sequential' ? 1 : effective) - active.length;
|
|
356
|
+
if (snapshot.usage.exhausted ||
|
|
357
|
+
snapshot.usage.durationMs >= snapshot.budgets.maxDurationMs ||
|
|
358
|
+
snapshot.usage.totalTokens >= snapshot.budgets.maxTokens)
|
|
359
|
+
return [];
|
|
360
|
+
if (remaining <= 0)
|
|
361
|
+
return [];
|
|
362
|
+
const selected = [];
|
|
363
|
+
const byId = new Map(snapshot.tasks.map((task) => [task.id, task]));
|
|
364
|
+
for (const task of snapshot.tasks) {
|
|
365
|
+
if (remaining === 0 ||
|
|
366
|
+
task.execution !== null ||
|
|
367
|
+
task.blockedBy.some((id) => {
|
|
368
|
+
const dependency = byId.get(id);
|
|
369
|
+
return (dependency === undefined ||
|
|
370
|
+
dependency.execution === null ||
|
|
371
|
+
dependency.execution.state !== 'completed' ||
|
|
372
|
+
dependency.execution.acceptance !== 'accepted');
|
|
373
|
+
}))
|
|
374
|
+
continue;
|
|
375
|
+
if (active.some((candidate) => teamTasksConflict(task, candidate)) ||
|
|
376
|
+
selected.some((candidate) => teamTasksConflict(task, candidate)))
|
|
377
|
+
continue;
|
|
378
|
+
selected.push(task);
|
|
379
|
+
remaining--;
|
|
380
|
+
}
|
|
381
|
+
return Object.freeze(selected.map((task) => task.id));
|
|
382
|
+
}
|
|
383
|
+
export function withTeamTaskExecution(snapshot, taskId, execution) {
|
|
384
|
+
const current = parseTeamSnapshot(snapshot);
|
|
385
|
+
const parsed = parseAgentLifecycleSnapshot(execution);
|
|
386
|
+
let found = false;
|
|
387
|
+
const tasks = current.tasks.map((task) => {
|
|
388
|
+
if (task.id !== taskId)
|
|
389
|
+
return task;
|
|
390
|
+
found = true;
|
|
391
|
+
return {
|
|
392
|
+
...task,
|
|
393
|
+
execution: parsed,
|
|
394
|
+
usage: task.usage.generation === parsed.generation
|
|
395
|
+
? task.usage
|
|
396
|
+
: { generation: parsed.generation, totalTokens: 0, durationMs: 0 },
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
if (!found)
|
|
400
|
+
throw new Error('Unknown task ID');
|
|
401
|
+
const totalTokens = tasks.reduce((sum, task) => sum + task.usage.totalTokens, 0);
|
|
402
|
+
return parseTeamSnapshot({
|
|
403
|
+
...current,
|
|
404
|
+
revision: current.revision + 1,
|
|
405
|
+
updatedAt: nextTeamTimestamp(current),
|
|
406
|
+
usage: { ...current.usage, totalTokens },
|
|
407
|
+
tasks,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
export function acceptTeamTaskExecution(snapshot, taskId, generation, acceptance = 'accepted') {
|
|
411
|
+
const current = parseTeamSnapshot(snapshot);
|
|
412
|
+
const task = current.tasks.find((candidate) => candidate.id === taskId);
|
|
413
|
+
if (!task || task.execution === null)
|
|
414
|
+
throw new Error('Task has no execution');
|
|
415
|
+
const execution = acceptLifecycle(task.execution, generation, acceptance);
|
|
416
|
+
return parseTeamSnapshot({
|
|
417
|
+
...current,
|
|
418
|
+
revision: current.revision + 1,
|
|
419
|
+
updatedAt: nextTeamTimestamp(current),
|
|
420
|
+
tasks: current.tasks.map((candidate) => candidate.id === taskId ? { ...candidate, execution } : candidate),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
export function recordTeamTaskProgress(snapshot, taskId, progress, exhaustedAt = new Date().toISOString()) {
|
|
424
|
+
const current = parseTeamSnapshot(snapshot);
|
|
425
|
+
if (!Number.isSafeInteger(progress.generation) ||
|
|
426
|
+
progress.generation < 1 ||
|
|
427
|
+
!Number.isSafeInteger(progress.totalTokens) ||
|
|
428
|
+
progress.totalTokens < 0 ||
|
|
429
|
+
!Number.isSafeInteger(progress.durationMs) ||
|
|
430
|
+
progress.durationMs < 0)
|
|
431
|
+
throw new Error('Invalid task progress');
|
|
432
|
+
const task = current.tasks.find((candidate) => candidate.id === taskId);
|
|
433
|
+
if (!task)
|
|
434
|
+
throw new Error('Unknown task ID');
|
|
435
|
+
if (!task.execution)
|
|
436
|
+
throw new Error('Task has no execution');
|
|
437
|
+
if (task.execution.generation !== progress.generation ||
|
|
438
|
+
task.usage.generation !== progress.generation)
|
|
439
|
+
throw new Error('Task progress generation mismatch');
|
|
440
|
+
const usage = task.usage;
|
|
441
|
+
if (progress.totalTokens < usage.totalTokens ||
|
|
442
|
+
progress.durationMs < usage.durationMs)
|
|
443
|
+
throw new Error('Regressing task progress');
|
|
444
|
+
const nextUsage = {
|
|
445
|
+
...usage,
|
|
446
|
+
totalTokens: Math.max(usage.totalTokens, progress.totalTokens),
|
|
447
|
+
durationMs: Math.max(usage.durationMs, progress.durationMs),
|
|
448
|
+
};
|
|
449
|
+
const totalTokens = current.tasks.reduce((sum, candidate) => sum +
|
|
450
|
+
(candidate.id === taskId
|
|
451
|
+
? nextUsage.totalTokens
|
|
452
|
+
: candidate.usage.totalTokens), 0);
|
|
453
|
+
let exhausted = current.usage.exhausted;
|
|
454
|
+
if (!exhausted && totalTokens >= current.budgets.maxTokens) {
|
|
455
|
+
exhausted = { reason: 'tokens', at: iso(exhaustedAt, 'exhaustion') };
|
|
456
|
+
}
|
|
457
|
+
return parseTeamSnapshot({
|
|
458
|
+
...current,
|
|
459
|
+
revision: current.revision + 1,
|
|
460
|
+
updatedAt: nextTeamTimestamp(current),
|
|
461
|
+
usage: {
|
|
462
|
+
...current.usage,
|
|
463
|
+
totalTokens,
|
|
464
|
+
exhausted,
|
|
465
|
+
},
|
|
466
|
+
tasks: current.tasks.map((candidate) => candidate.id === taskId ? { ...candidate, usage: nextUsage } : candidate),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
export function updateTeamUsageDuration(snapshot, durationMs, exhaustedAt = new Date().toISOString()) {
|
|
470
|
+
const current = parseTeamSnapshot(snapshot);
|
|
471
|
+
if (!Number.isSafeInteger(durationMs) || durationMs < 0)
|
|
472
|
+
throw new Error('Invalid Team duration');
|
|
473
|
+
if (durationMs < current.usage.durationMs)
|
|
474
|
+
throw new Error('Regressing Team duration');
|
|
475
|
+
let exhausted = current.usage.exhausted;
|
|
476
|
+
if (!exhausted && durationMs >= current.budgets.maxDurationMs) {
|
|
477
|
+
exhausted = { reason: 'duration', at: iso(exhaustedAt, 'exhaustion') };
|
|
478
|
+
}
|
|
479
|
+
return parseTeamSnapshot({
|
|
480
|
+
...current,
|
|
481
|
+
revision: current.revision + 1,
|
|
482
|
+
updatedAt: nextTeamTimestamp(current),
|
|
483
|
+
usage: {
|
|
484
|
+
...current.usage,
|
|
485
|
+
durationMs,
|
|
486
|
+
exhausted,
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
export function markTeamBudgetExhausted(snapshot, reason, at = new Date().toISOString()) {
|
|
491
|
+
const current = parseTeamSnapshot(snapshot);
|
|
492
|
+
iso(at, 'exhaustion');
|
|
493
|
+
if (current.usage.exhausted)
|
|
494
|
+
return current;
|
|
495
|
+
return parseTeamSnapshot({
|
|
496
|
+
...current,
|
|
497
|
+
revision: current.revision + 1,
|
|
498
|
+
updatedAt: nextTeamTimestamp(current),
|
|
499
|
+
usage: {
|
|
500
|
+
...current.usage,
|
|
501
|
+
exhausted: { reason, at },
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=team-ownership.js.map
|
|
@@ -24,7 +24,12 @@ export class ToolExecutionScheduler {
|
|
|
24
24
|
this.precedingBarrier,
|
|
25
25
|
...this.concurrentGroup,
|
|
26
26
|
]).then(() => undefined)
|
|
27
|
-
:
|
|
27
|
+
: policy.startAfterAssistant
|
|
28
|
+
? Promise.allSettled([
|
|
29
|
+
this.exclusiveStart,
|
|
30
|
+
this.precedingBarrier,
|
|
31
|
+
]).then(() => undefined)
|
|
32
|
+
: this.precedingBarrier;
|
|
28
33
|
const signal = this.executionSignal(policy);
|
|
29
34
|
const execution = predecessors.then(async () => {
|
|
30
35
|
let result;
|
|
@@ -23,7 +23,11 @@ export interface TranscriptContextSummaryEvent extends TranscriptEventIdentity {
|
|
|
23
23
|
readonly kind: 'context-summary';
|
|
24
24
|
readonly summary: string;
|
|
25
25
|
}
|
|
26
|
-
export
|
|
26
|
+
export interface TranscriptToolExecutionStartedEvent extends TranscriptEventIdentity {
|
|
27
|
+
readonly kind: 'tool-execution-started';
|
|
28
|
+
readonly callId: string;
|
|
29
|
+
}
|
|
30
|
+
export type TranscriptEvent = TranscriptMessagesEvent | TranscriptContextBoundaryEvent | TranscriptContextSummaryEvent | TranscriptToolExecutionStartedEvent;
|
|
27
31
|
export declare function isModelImage(value: unknown): value is ModelImage;
|
|
28
32
|
export declare function isModelDocument(value: unknown): value is ModelDocument;
|
|
29
33
|
export declare function isModelContentBlock(value: unknown): value is ModelContentBlock;
|
|
@@ -139,6 +139,18 @@ export function isTranscriptEvent(value) {
|
|
|
139
139
|
!hasValidIdentity(value) ||
|
|
140
140
|
typeof value.kind !== 'string')
|
|
141
141
|
return false;
|
|
142
|
+
if (value.kind === 'tool-execution-started') {
|
|
143
|
+
return (hasOnlyKeys(value, [
|
|
144
|
+
'kind',
|
|
145
|
+
'id',
|
|
146
|
+
'parentId',
|
|
147
|
+
'sessionId',
|
|
148
|
+
'timestamp',
|
|
149
|
+
'callId',
|
|
150
|
+
]) &&
|
|
151
|
+
isNonEmptyString(value.callId) &&
|
|
152
|
+
value.callId.trim().length > 0);
|
|
153
|
+
}
|
|
142
154
|
if (value.kind === 'messages') {
|
|
143
155
|
return (hasOnlyKeys(value, [
|
|
144
156
|
'kind',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ClaudeSchemaAdapter, ClaudeTranscriptEntry } from '../compatibility/claude/schema.js';
|
|
2
2
|
import { type ClaudeSessionIndex } from './claude-session-index.js';
|
|
3
|
+
import { classifyTranscriptAppend } from './transcript-file-append.js';
|
|
4
|
+
export { classifyTranscriptAppend };
|
|
3
5
|
export interface TranscriptTail {
|
|
4
6
|
byteLength: number;
|
|
5
7
|
lastLineHash: string | null;
|
|
@@ -65,7 +67,6 @@ export interface ClaudeTranscriptStoreOptions {
|
|
|
65
67
|
schema: ClaudeSchemaAdapter;
|
|
66
68
|
writeProfile?: 'main' | 'sidechain';
|
|
67
69
|
}
|
|
68
|
-
export declare function classifyTranscriptAppend(written: Uint8Array, previousByteLength: number, encodedLine: Uint8Array): 'appended' | 'interleaved-write';
|
|
69
70
|
export declare class ClaudeTranscriptStore {
|
|
70
71
|
private readonly sessionFile;
|
|
71
72
|
private readonly lease;
|
|
@@ -7,6 +7,8 @@ import { isClaudeDurableMetadataType } from '../compatibility/claude/session-met
|
|
|
7
7
|
import { getClaudeContentBlocks, indexClaudeToolLinks, recoverClaudeToolResultLinks, } from '../compatibility/claude/tool-links.js';
|
|
8
8
|
import { ExclusiveFileLease } from '../platform/exclusive-file-lease.js';
|
|
9
9
|
import { readClaudeSessionIndex, } from './claude-session-index.js';
|
|
10
|
+
import { classifyTranscriptAppend } from './transcript-file-append.js';
|
|
11
|
+
export { classifyTranscriptAppend };
|
|
10
12
|
export class ClaudeTranscriptParseError extends Error {
|
|
11
13
|
lineNumber;
|
|
12
14
|
byteOffset;
|
|
@@ -34,16 +36,6 @@ const NON_TAIL_ENTRY_TYPES = new Set([
|
|
|
34
36
|
'tag',
|
|
35
37
|
'worktree-state',
|
|
36
38
|
]);
|
|
37
|
-
export function classifyTranscriptAppend(written, previousByteLength, encodedLine) {
|
|
38
|
-
const expectedEnd = previousByteLength + encodedLine.length;
|
|
39
|
-
if (written.length === expectedEnd &&
|
|
40
|
-
Buffer.from(written)
|
|
41
|
-
.subarray(previousByteLength, expectedEnd)
|
|
42
|
-
.equals(encodedLine)) {
|
|
43
|
-
return 'appended';
|
|
44
|
-
}
|
|
45
|
-
return 'interleaved-write';
|
|
46
|
-
}
|
|
47
39
|
function hashLine(line) {
|
|
48
40
|
return createHash('sha256').update(line).digest('hex');
|
|
49
41
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type TeamMailboxMessage, type TeamMailboxPayload } from '../core/team-mailbox.js';
|
|
2
|
+
export interface NativeTeamMailboxRetention {
|
|
3
|
+
readonly maxRecords?: number;
|
|
4
|
+
readonly maxBytes?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NativeTeamMailboxOptions {
|
|
7
|
+
readonly nativeRoot: string;
|
|
8
|
+
readonly projectIdentity: string;
|
|
9
|
+
readonly teamId: string;
|
|
10
|
+
readonly participants: readonly string[];
|
|
11
|
+
readonly retention?: NativeTeamMailboxRetention;
|
|
12
|
+
}
|
|
13
|
+
export interface NativeTeamMailboxAppendInput {
|
|
14
|
+
readonly messageId: string;
|
|
15
|
+
readonly sender: string;
|
|
16
|
+
readonly recipients: readonly string[];
|
|
17
|
+
readonly payload: TeamMailboxPayload;
|
|
18
|
+
readonly createdAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface NativeTeamMailboxAppendResult {
|
|
21
|
+
readonly message: TeamMailboxMessage;
|
|
22
|
+
readonly inserted: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface NativeTeamMailboxReadProof {
|
|
25
|
+
readonly participant: string;
|
|
26
|
+
readonly expectedCursor: number;
|
|
27
|
+
readonly throughSequence: number;
|
|
28
|
+
readonly messageIds: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
export interface NativeTeamMailboxReadResult {
|
|
31
|
+
readonly messages: readonly TeamMailboxMessage[];
|
|
32
|
+
readonly expectedCursor: number;
|
|
33
|
+
readonly throughSequence: number;
|
|
34
|
+
readonly messageIds: readonly string[];
|
|
35
|
+
readonly proof: NativeTeamMailboxReadProof;
|
|
36
|
+
}
|
|
37
|
+
export declare class NativeTeamMailbox {
|
|
38
|
+
private readonly options;
|
|
39
|
+
readonly messagesPath: string;
|
|
40
|
+
readonly statePath: string;
|
|
41
|
+
readonly mutationPath: string;
|
|
42
|
+
private readonly participants;
|
|
43
|
+
private readonly participantSet;
|
|
44
|
+
private readonly maxRecords;
|
|
45
|
+
private readonly maxBytes;
|
|
46
|
+
private mutation;
|
|
47
|
+
private constructor();
|
|
48
|
+
static open(options: NativeTeamMailboxOptions): NativeTeamMailbox;
|
|
49
|
+
append(input: NativeTeamMailboxAppendInput): Promise<NativeTeamMailboxAppendResult>;
|
|
50
|
+
read(participant: string, bounds?: {
|
|
51
|
+
maxMessages?: number;
|
|
52
|
+
}): Promise<NativeTeamMailboxReadResult>;
|
|
53
|
+
acknowledge(proof: NativeTeamMailboxReadProof): Promise<void>;
|
|
54
|
+
private enqueue;
|
|
55
|
+
private mutate;
|
|
56
|
+
private acquireMutation;
|
|
57
|
+
private load;
|
|
58
|
+
private truncateAndSync;
|
|
59
|
+
private rewriteRecords;
|
|
60
|
+
private ensureMode;
|
|
61
|
+
private loadState;
|
|
62
|
+
private initialState;
|
|
63
|
+
private writeState;
|
|
64
|
+
private compact;
|
|
65
|
+
private assertParticipant;
|
|
66
|
+
private assertParticipants;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=native-team-mailbox.d.ts.map
|