praxis-agent 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- 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/session-service.d.ts +8 -4
- package/dist/application/session-service.js +528 -434
- package/dist/application/subagent-service.js +358 -177
- 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 +33 -0
- package/dist/application/transcript-projection.js +110 -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.js +264 -204
- package/dist/cli/tui/claude-style.js +154 -130
- 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 +9 -5
- package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli-runtime.d.ts +2 -2
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/runtime.d.ts +5 -0
- package/dist/core/runtime.js +15 -1
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- package/dist/persistence/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Praxis
|
|
2
2
|
|
|
3
|
+
[English](README.md) | [简体中文](README_zh.md)
|
|
4
|
+
|
|
3
5
|
[](https://github.com/Forest-Isle/Praxis/actions/workflows/ci.yml)
|
|
4
6
|
[](https://github.com/Forest-Isle/Praxis/actions/workflows/codeql.yml)
|
|
5
7
|
[](https://scorecard.dev/viewer/?uri=github.com/Forest-Isle/Praxis)
|
|
@@ -148,7 +150,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
148
150
|
- **Durable local work** — resumable sessions, full-history forks, file
|
|
149
151
|
checkpoints, tasks, foreground/background subagents, top-level agents, and
|
|
150
152
|
Claude-compatible main-thread agent definitions with native prompt, model,
|
|
151
|
-
tool, memory, first-turn, and resume behavior.
|
|
153
|
+
tool, memory, first-turn, and resume behavior. Agent execution uses one
|
|
154
|
+
durable lifecycle vocabulary with bounded cancellation and drain,
|
|
155
|
+
continuation, notifications, and single-owner orphan recovery.
|
|
152
156
|
- **Claude-compatible ecosystem** — shared instructions with recursive `@`
|
|
153
157
|
imports, memory, skills, commands, agents, hooks, settings, MCP servers,
|
|
154
158
|
plugins, and transcript data.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelUsage, ModelUsageByModel } from '../core/runtime.js';
|
|
2
|
+
import { type LifecycleState } from '../core/agent-orchestration.js';
|
|
2
3
|
export interface BackgroundAgentRunResult {
|
|
3
4
|
text: string;
|
|
4
5
|
usage: ModelUsage;
|
|
@@ -19,6 +20,10 @@ export declare class BackgroundAgentRunError extends Error {
|
|
|
19
20
|
export declare class BackgroundAgentShutdownError extends Error {
|
|
20
21
|
constructor();
|
|
21
22
|
}
|
|
23
|
+
export interface BackgroundAgentLifecycleSource {
|
|
24
|
+
current(): LifecycleState;
|
|
25
|
+
subscribe(listener: (state: LifecycleState) => void): () => void;
|
|
26
|
+
}
|
|
22
27
|
export interface BackgroundAgentTaskSpec {
|
|
23
28
|
agentId: string;
|
|
24
29
|
name?: string;
|
|
@@ -28,13 +33,15 @@ export interface BackgroundAgentTaskSpec {
|
|
|
28
33
|
toolUseId: string;
|
|
29
34
|
outputFile: string;
|
|
30
35
|
resolvedModel: string;
|
|
36
|
+
lifecycle: BackgroundAgentLifecycleSource;
|
|
31
37
|
markBackground?(): void;
|
|
32
38
|
acknowledgeNotification?(notificationId: string): Promise<void>;
|
|
33
39
|
prepareNotificationDetached?(notificationId: string, model: string): Promise<void>;
|
|
34
40
|
confirmNotificationDetached?(notificationId: string): Promise<void>;
|
|
35
41
|
run(message: string, signal: AbortSignal, continuation: boolean, toolUseId: string): Promise<BackgroundAgentRunResult>;
|
|
36
42
|
}
|
|
37
|
-
type BackgroundAgentStatus =
|
|
43
|
+
type BackgroundAgentStatus = LifecycleState;
|
|
44
|
+
export declare function legacyBackgroundStatus(state: BackgroundAgentStatus): 'running' | 'stopped' | 'interrupted' | 'completed' | 'failed';
|
|
38
45
|
export interface BackgroundAgentNotificationIdentity {
|
|
39
46
|
agentId: string;
|
|
40
47
|
toolUseId: string;
|
|
@@ -66,12 +73,15 @@ export declare class BackgroundAgentManager {
|
|
|
66
73
|
operation: Promise<BackgroundAgentRunResult>;
|
|
67
74
|
startedAt: number;
|
|
68
75
|
}): BackgroundAgentSnapshot;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
registerPersisted(spec: BackgroundAgentTaskSpec, presentation?: {
|
|
77
|
+
result?: BackgroundAgentRunResult | null;
|
|
78
|
+
error?: string | null;
|
|
79
|
+
startedAt?: number;
|
|
80
|
+
durationMs?: number | null;
|
|
81
|
+
}): BackgroundAgentSnapshot;
|
|
72
82
|
registerPersistedNotification(agentId: string, notification: {
|
|
73
83
|
id: string;
|
|
74
|
-
status: 'completed' | 'failed' | '
|
|
84
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
75
85
|
result: BackgroundAgentRunResult | null;
|
|
76
86
|
error: string | null;
|
|
77
87
|
toolUseId: string;
|
|
@@ -112,7 +122,7 @@ export declare class BackgroundAgentManager {
|
|
|
112
122
|
private resolveOptional;
|
|
113
123
|
private stopTask;
|
|
114
124
|
private assertNameAvailable;
|
|
115
|
-
private
|
|
125
|
+
private finishCancelled;
|
|
116
126
|
private resolveRequired;
|
|
117
127
|
private snapshot;
|
|
118
128
|
private formatOutput;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { isTerminalLifecycleState, } from '../core/agent-orchestration.js';
|
|
2
3
|
import { isClaudeAgentId } from '../compatibility/claude/sidechain.js';
|
|
3
4
|
const MAX_TIMEOUT_MS = 600_000;
|
|
4
5
|
const DEFAULT_CLOSE_DRAIN_MS = 5_000;
|
|
@@ -16,6 +17,25 @@ export class BackgroundAgentShutdownError extends Error {
|
|
|
16
17
|
this.name = 'BackgroundAgentShutdownError';
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
export function legacyBackgroundStatus(state) {
|
|
21
|
+
if (state === 'queued' ||
|
|
22
|
+
state === 'running' ||
|
|
23
|
+
state === 'waiting' ||
|
|
24
|
+
state === 'cancelling')
|
|
25
|
+
return 'running';
|
|
26
|
+
if (state === 'cancelled')
|
|
27
|
+
return 'stopped';
|
|
28
|
+
if (state === 'orphaned')
|
|
29
|
+
return 'interrupted';
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
function notificationWireStatus(status) {
|
|
33
|
+
if (status === 'completed' || status === 'failed')
|
|
34
|
+
return status;
|
|
35
|
+
if (status === 'cancelled')
|
|
36
|
+
return 'killed';
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
19
39
|
function assertAgentId(agentId) {
|
|
20
40
|
if (!isClaudeAgentId(agentId)) {
|
|
21
41
|
throw new Error(`Invalid background agent ID: ${agentId}`);
|
|
@@ -174,11 +194,11 @@ export class BackgroundAgentManager {
|
|
|
174
194
|
}
|
|
175
195
|
launch(spec) {
|
|
176
196
|
const { task } = this.registerTask(spec, {
|
|
177
|
-
status:
|
|
197
|
+
status: spec.lifecycle.current(),
|
|
178
198
|
controller: null,
|
|
179
199
|
result: null,
|
|
180
200
|
error: null,
|
|
181
|
-
|
|
201
|
+
runSequence: 0,
|
|
182
202
|
startedAt: Date.now(),
|
|
183
203
|
durationMs: null,
|
|
184
204
|
}, 'throw');
|
|
@@ -189,11 +209,11 @@ export class BackgroundAgentManager {
|
|
|
189
209
|
adopt(options) {
|
|
190
210
|
const { spec } = options;
|
|
191
211
|
const { task } = this.registerTask(spec, {
|
|
192
|
-
status:
|
|
212
|
+
status: spec.lifecycle.current(),
|
|
193
213
|
controller: options.controller,
|
|
194
214
|
result: null,
|
|
195
215
|
error: null,
|
|
196
|
-
|
|
216
|
+
runSequence: 1,
|
|
197
217
|
startedAt: options.startedAt,
|
|
198
218
|
durationMs: null,
|
|
199
219
|
}, 'throw');
|
|
@@ -201,39 +221,18 @@ export class BackgroundAgentManager {
|
|
|
201
221
|
this.track(task, options.operation, 1, spec.toolUseId);
|
|
202
222
|
return this.snapshot(task);
|
|
203
223
|
}
|
|
204
|
-
|
|
205
|
-
const { task } = this.registerTask(spec, {
|
|
206
|
-
status: 'completed',
|
|
207
|
-
controller: null,
|
|
208
|
-
result,
|
|
209
|
-
error: null,
|
|
210
|
-
generation: 0,
|
|
211
|
-
startedAt: Date.now() - result.durationMs,
|
|
212
|
-
durationMs: result.durationMs,
|
|
213
|
-
}, 'return-existing');
|
|
214
|
-
return this.snapshot(task);
|
|
215
|
-
}
|
|
216
|
-
registerInterrupted(spec, error = 'Persisted agent was interrupted before completion') {
|
|
224
|
+
registerPersisted(spec, presentation = {}) {
|
|
217
225
|
const { task } = this.registerTask(spec, {
|
|
218
|
-
status:
|
|
226
|
+
status: spec.lifecycle.current(),
|
|
219
227
|
controller: null,
|
|
220
|
-
result: null,
|
|
221
|
-
error,
|
|
222
|
-
|
|
223
|
-
startedAt:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
registerTerminal(spec, status, error) {
|
|
229
|
-
const { task } = this.registerTask(spec, {
|
|
230
|
-
status,
|
|
231
|
-
controller: null,
|
|
232
|
-
result: null,
|
|
233
|
-
error,
|
|
234
|
-
generation: 0,
|
|
235
|
-
startedAt: Date.now(),
|
|
236
|
-
durationMs: 0,
|
|
228
|
+
result: presentation.result ?? null,
|
|
229
|
+
error: presentation.error ?? null,
|
|
230
|
+
runSequence: 0,
|
|
231
|
+
startedAt: presentation.startedAt ??
|
|
232
|
+
(presentation.durationMs === undefined
|
|
233
|
+
? Date.now()
|
|
234
|
+
: Date.now() - (presentation.durationMs ?? 0)),
|
|
235
|
+
durationMs: presentation.durationMs ?? null,
|
|
237
236
|
}, 'return-existing');
|
|
238
237
|
return this.snapshot(task);
|
|
239
238
|
}
|
|
@@ -250,7 +249,12 @@ export class BackgroundAgentManager {
|
|
|
250
249
|
}
|
|
251
250
|
notificationClaimAgentIds() {
|
|
252
251
|
return [...this.tasks.entries()]
|
|
253
|
-
.filter(([, task]) => task.
|
|
252
|
+
.filter(([, task]) => task.promise !== null ||
|
|
253
|
+
task.status === 'queued' ||
|
|
254
|
+
task.status === 'running' ||
|
|
255
|
+
task.status === 'waiting' ||
|
|
256
|
+
task.status === 'cancelling' ||
|
|
257
|
+
task.notifications.length > 0)
|
|
254
258
|
.map(([agentId]) => agentId);
|
|
255
259
|
}
|
|
256
260
|
snapshotById(agentId) {
|
|
@@ -281,7 +285,7 @@ export class BackgroundAgentManager {
|
|
|
281
285
|
// The retrieval outcome reflects the task's state after any wait: success
|
|
282
286
|
// once terminal, not_ready for non-blocking retrievals, and timeout for a
|
|
283
287
|
// blocking retrieval whose window (including zero) closed while live.
|
|
284
|
-
const retrieval = task.status
|
|
288
|
+
const retrieval = isTerminalLifecycleState(task.status)
|
|
285
289
|
? 'success'
|
|
286
290
|
: options.block
|
|
287
291
|
? 'timeout'
|
|
@@ -299,14 +303,21 @@ export class BackgroundAgentManager {
|
|
|
299
303
|
async stopAndWait(agentId, timeout = DEFAULT_CLOSE_DRAIN_MS) {
|
|
300
304
|
const message = this.stop(agentId);
|
|
301
305
|
const task = this.tasks.get(agentId);
|
|
302
|
-
if (task?.promise)
|
|
306
|
+
if (task?.promise) {
|
|
303
307
|
await waitBounded(task.promise, timeout);
|
|
308
|
+
}
|
|
309
|
+
if (!task || task.promise || !isTerminalLifecycleState(task.status)) {
|
|
310
|
+
return `Task ${agentId} cancellation is still in progress`;
|
|
311
|
+
}
|
|
312
|
+
if (task.status !== 'cancelled') {
|
|
313
|
+
return `Task ${agentId} cancellation ended with status ${legacyBackgroundStatus(task.status)}`;
|
|
314
|
+
}
|
|
304
315
|
return message;
|
|
305
316
|
}
|
|
306
317
|
stopAll() {
|
|
307
318
|
const stopped = [];
|
|
308
319
|
for (const task of this.tasks.values()) {
|
|
309
|
-
if (task.
|
|
320
|
+
if (!task.promise || !task.controller || task.cancellationRequested)
|
|
310
321
|
continue;
|
|
311
322
|
this.stopTask(task, 'Stopped by explicit bulk kill');
|
|
312
323
|
stopped.push(task.spec.agentId);
|
|
@@ -325,8 +336,8 @@ export class BackgroundAgentManager {
|
|
|
325
336
|
this.resolveClosed();
|
|
326
337
|
const running = [];
|
|
327
338
|
for (const task of this.tasks.values()) {
|
|
328
|
-
if (task.
|
|
329
|
-
task.
|
|
339
|
+
if (task.promise && task.controller) {
|
|
340
|
+
task.cancellationRequested = true;
|
|
330
341
|
task.error = 'Stopped because the background agent manager closed';
|
|
331
342
|
task.suppressNotifications = true;
|
|
332
343
|
task.notifications.length = 0;
|
|
@@ -337,6 +348,8 @@ export class BackgroundAgentManager {
|
|
|
337
348
|
running.push(task.promise);
|
|
338
349
|
}
|
|
339
350
|
await waitBounded(Promise.allSettled(running).then(() => undefined), drainMilliseconds);
|
|
351
|
+
for (const task of this.tasks.values())
|
|
352
|
+
task.unsubscribeLifecycle?.();
|
|
340
353
|
this.tasks.clear();
|
|
341
354
|
this.names.clear();
|
|
342
355
|
}
|
|
@@ -355,11 +368,21 @@ export class BackgroundAgentManager {
|
|
|
355
368
|
});
|
|
356
369
|
}
|
|
357
370
|
const priorStatus = task.status;
|
|
358
|
-
if (task.
|
|
371
|
+
if (task.cancellationRequested &&
|
|
372
|
+
(task.promise !== null || task.status !== 'cancelled')) {
|
|
373
|
+
return JSON.stringify({
|
|
374
|
+
success: false,
|
|
375
|
+
message: `Agent "${agentId}" is cancelling and cannot receive a continuation yet.`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (task.promise) {
|
|
359
379
|
task.queuedMessages.push({ message, toolUseId });
|
|
360
380
|
}
|
|
361
381
|
else {
|
|
362
|
-
|
|
382
|
+
task.queuedMessages.push({ message, toolUseId });
|
|
383
|
+
const next = task.queuedMessages.shift();
|
|
384
|
+
if (next)
|
|
385
|
+
this.start(task, next.message, true, next.toolUseId);
|
|
363
386
|
}
|
|
364
387
|
return JSON.stringify({
|
|
365
388
|
success: true,
|
|
@@ -373,7 +396,10 @@ export class BackgroundAgentManager {
|
|
|
373
396
|
!eligibleTasks.some(([, task]) => task.notifications.length > 0)) {
|
|
374
397
|
const running = eligibleTasks
|
|
375
398
|
.map(([, task]) => task)
|
|
376
|
-
.filter((task) => task.
|
|
399
|
+
.filter((task) => task.promise !== null ||
|
|
400
|
+
task.status === 'queued' ||
|
|
401
|
+
task.status === 'running' ||
|
|
402
|
+
task.status === 'cancelling')
|
|
377
403
|
.map((task) => task.promise)
|
|
378
404
|
.filter((promise) => promise !== null);
|
|
379
405
|
if (running.length > 0) {
|
|
@@ -386,12 +412,16 @@ export class BackgroundAgentManager {
|
|
|
386
412
|
let durationApiMs = 0;
|
|
387
413
|
let durationApiWithoutRetriesMs = 0;
|
|
388
414
|
let durationSeen = false;
|
|
389
|
-
const
|
|
415
|
+
const consumedNotifications = [];
|
|
390
416
|
for (const [, task] of eligibleTasks) {
|
|
391
417
|
if (task.notifications.length === 0)
|
|
392
418
|
continue;
|
|
393
419
|
for (const notification of task.notifications) {
|
|
394
|
-
|
|
420
|
+
const message = this.formatNotification(task, notification);
|
|
421
|
+
if (message === null)
|
|
422
|
+
continue;
|
|
423
|
+
notifications.push(message);
|
|
424
|
+
consumedNotifications.push({ task, notification });
|
|
395
425
|
if (notification.result) {
|
|
396
426
|
assertValidResultUsage(notification.result.usage);
|
|
397
427
|
usage = addUsageChecked(undefined, usage, notification.result.usage);
|
|
@@ -407,18 +437,13 @@ export class BackgroundAgentManager {
|
|
|
407
437
|
durationApiWithoutRetriesMs = addApiDuration(notification.result.durationApiWithoutRetriesMs ?? total, durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
|
|
408
438
|
}
|
|
409
439
|
}
|
|
410
|
-
consumedTasks.push(task);
|
|
411
440
|
}
|
|
412
441
|
const modelUsage = modelUsageByModel.size === 0
|
|
413
442
|
? undefined
|
|
414
443
|
: Object.fromEntries(modelUsageByModel);
|
|
415
444
|
if (options.consume !== false) {
|
|
416
|
-
for (const task of
|
|
417
|
-
|
|
418
|
-
continue;
|
|
419
|
-
for (const notification of task.notifications) {
|
|
420
|
-
await task.spec.acknowledgeNotification(notification.id);
|
|
421
|
-
}
|
|
445
|
+
for (const { task, notification } of consumedNotifications) {
|
|
446
|
+
await task.spec.acknowledgeNotification?.(notification.id);
|
|
422
447
|
}
|
|
423
448
|
}
|
|
424
449
|
const result = {
|
|
@@ -428,8 +453,9 @@ export class BackgroundAgentManager {
|
|
|
428
453
|
...(durationSeen ? { durationApiMs, durationApiWithoutRetriesMs } : {}),
|
|
429
454
|
};
|
|
430
455
|
if (options.consume !== false) {
|
|
431
|
-
for (const task of
|
|
432
|
-
task.notifications.splice(
|
|
456
|
+
for (const { task, notification } of consumedNotifications) {
|
|
457
|
+
task.notifications.splice(task.notifications.indexOf(notification), 1);
|
|
458
|
+
}
|
|
433
459
|
}
|
|
434
460
|
return result;
|
|
435
461
|
}
|
|
@@ -454,6 +480,8 @@ export class BackgroundAgentManager {
|
|
|
454
480
|
for (const task of this.tasks.values()) {
|
|
455
481
|
for (const notification of [...task.notifications]) {
|
|
456
482
|
const message = this.formatNotification(task, notification);
|
|
483
|
+
if (message === null)
|
|
484
|
+
continue;
|
|
457
485
|
const index = remaining.indexOf(message);
|
|
458
486
|
if (index < 0)
|
|
459
487
|
continue;
|
|
@@ -465,9 +493,9 @@ export class BackgroundAgentManager {
|
|
|
465
493
|
async acknowledgeDelivered(delivered) {
|
|
466
494
|
for (const [agentId, task] of this.tasks) {
|
|
467
495
|
for (const notification of [...task.notifications]) {
|
|
468
|
-
|
|
496
|
+
const status = notificationWireStatus(notification.status);
|
|
497
|
+
if (status === null)
|
|
469
498
|
continue;
|
|
470
|
-
const status = notification.status === 'stopped' ? 'killed' : notification.status;
|
|
471
499
|
if (!delivered({ agentId, toolUseId: notification.toolUseId, status })) {
|
|
472
500
|
continue;
|
|
473
501
|
}
|
|
@@ -479,9 +507,9 @@ export class BackgroundAgentManager {
|
|
|
479
507
|
async acknowledgeDeliveredAsDetached(delivered) {
|
|
480
508
|
for (const [agentId, task] of this.tasks) {
|
|
481
509
|
for (const notification of [...task.notifications]) {
|
|
482
|
-
|
|
510
|
+
const status = notificationWireStatus(notification.status);
|
|
511
|
+
if (status === null)
|
|
483
512
|
continue;
|
|
484
|
-
const status = notification.status === 'stopped' ? 'killed' : notification.status;
|
|
485
513
|
if (!delivered({ agentId, toolUseId: notification.toolUseId, status })) {
|
|
486
514
|
continue;
|
|
487
515
|
}
|
|
@@ -493,67 +521,71 @@ export class BackgroundAgentManager {
|
|
|
493
521
|
}
|
|
494
522
|
}
|
|
495
523
|
start(task, message, continuation, toolUseId) {
|
|
496
|
-
task.
|
|
497
|
-
const
|
|
524
|
+
task.runSequence += 1;
|
|
525
|
+
const runSequence = task.runSequence;
|
|
498
526
|
const controller = new AbortController();
|
|
499
|
-
task.
|
|
527
|
+
task.cancellationRequested = false;
|
|
500
528
|
task.startedAt = Date.now();
|
|
501
529
|
task.durationMs = null;
|
|
502
530
|
task.controller = controller;
|
|
503
531
|
task.result = null;
|
|
504
532
|
task.error = null;
|
|
505
|
-
this.track(task, task.spec.run(message, controller.signal, continuation, toolUseId),
|
|
533
|
+
this.track(task, task.spec.run(message, controller.signal, continuation, toolUseId), runSequence, toolUseId);
|
|
506
534
|
}
|
|
507
|
-
track(task, operation,
|
|
535
|
+
track(task, operation, runSequence, toolUseId) {
|
|
508
536
|
task.promise = operation
|
|
509
537
|
.then((result) => {
|
|
510
|
-
if (task.
|
|
538
|
+
if (task.runSequence !== runSequence)
|
|
511
539
|
return;
|
|
512
|
-
if (task.status === '
|
|
513
|
-
this.
|
|
540
|
+
if (task.status === 'cancelled') {
|
|
541
|
+
this.finishCancelled(task, result);
|
|
514
542
|
return;
|
|
515
543
|
}
|
|
516
|
-
task.status = 'completed';
|
|
517
544
|
task.result = result;
|
|
518
545
|
task.durationMs = result.durationMs;
|
|
519
546
|
task.error = null;
|
|
520
|
-
task.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
547
|
+
if (task.status === 'completed')
|
|
548
|
+
task.notifications.push({
|
|
549
|
+
id: result.notificationId ?? randomUUID(),
|
|
550
|
+
status: 'completed',
|
|
551
|
+
result,
|
|
552
|
+
error: null,
|
|
553
|
+
toolUseId,
|
|
554
|
+
});
|
|
527
555
|
})
|
|
528
556
|
.catch((error) => {
|
|
529
|
-
if (task.
|
|
557
|
+
if (task.runSequence !== runSequence)
|
|
530
558
|
return;
|
|
531
|
-
if (task.status === '
|
|
532
|
-
this.
|
|
559
|
+
if (task.status === 'cancelled') {
|
|
560
|
+
this.finishCancelled(task, error instanceof BackgroundAgentRunError ? error.result : undefined);
|
|
533
561
|
return;
|
|
534
562
|
}
|
|
535
563
|
const failedResult = error instanceof BackgroundAgentRunError ? error.result : undefined;
|
|
536
|
-
task.status = 'failed';
|
|
537
564
|
task.result = null;
|
|
538
565
|
task.durationMs =
|
|
539
566
|
failedResult?.durationMs ?? Date.now() - task.startedAt;
|
|
540
567
|
task.error = error instanceof Error ? error.message : String(error);
|
|
541
|
-
task.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
568
|
+
if (task.status === 'failed')
|
|
569
|
+
task.notifications.push({
|
|
570
|
+
id: failedResult?.notificationId ?? randomUUID(),
|
|
571
|
+
status: 'failed',
|
|
572
|
+
result: failedResult ?? null,
|
|
573
|
+
error: task.error,
|
|
574
|
+
toolUseId,
|
|
575
|
+
});
|
|
548
576
|
})
|
|
549
577
|
.finally(() => {
|
|
550
|
-
if (task.
|
|
578
|
+
if (task.runSequence !== runSequence)
|
|
551
579
|
return;
|
|
552
580
|
task.controller = null;
|
|
553
581
|
task.promise = null;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
582
|
+
if (!task.cancellationRequested &&
|
|
583
|
+
(task.status === 'completed' ||
|
|
584
|
+
task.status === 'failed' ||
|
|
585
|
+
task.status === 'cancelled')) {
|
|
586
|
+
const next = task.queuedMessages.shift();
|
|
587
|
+
if (next)
|
|
588
|
+
this.start(task, next.message, true, next.toolUseId);
|
|
557
589
|
}
|
|
558
590
|
});
|
|
559
591
|
}
|
|
@@ -582,7 +614,13 @@ export class BackgroundAgentManager {
|
|
|
582
614
|
notifications: [],
|
|
583
615
|
queuedMessages: [],
|
|
584
616
|
suppressNotifications: false,
|
|
617
|
+
cancellationRequested: false,
|
|
618
|
+
unsubscribeLifecycle: null,
|
|
585
619
|
};
|
|
620
|
+
task.unsubscribeLifecycle = spec.lifecycle.subscribe((state) => {
|
|
621
|
+
if (!this.closed)
|
|
622
|
+
task.status = state;
|
|
623
|
+
});
|
|
586
624
|
this.tasks.set(spec.agentId, task);
|
|
587
625
|
return { task, created: true };
|
|
588
626
|
}
|
|
@@ -592,10 +630,11 @@ export class BackgroundAgentManager {
|
|
|
592
630
|
return this.names.get(identifier);
|
|
593
631
|
}
|
|
594
632
|
stopTask(task, reason) {
|
|
595
|
-
if (task.
|
|
596
|
-
|
|
633
|
+
if (!task.promise || !task.controller || task.cancellationRequested) {
|
|
634
|
+
const status = legacyBackgroundStatus(task.status);
|
|
635
|
+
throw new Error(`Task ${task.spec.agentId} is not running (status: ${status})`);
|
|
597
636
|
}
|
|
598
|
-
task.
|
|
637
|
+
task.cancellationRequested = true;
|
|
599
638
|
task.error = reason;
|
|
600
639
|
task.durationMs ??= Date.now() - task.startedAt;
|
|
601
640
|
task.queuedMessages.length = 0;
|
|
@@ -609,7 +648,7 @@ export class BackgroundAgentManager {
|
|
|
609
648
|
throw new Error(`Background agent name already exists: ${name}`);
|
|
610
649
|
}
|
|
611
650
|
}
|
|
612
|
-
|
|
651
|
+
finishCancelled(task, result) {
|
|
613
652
|
const stoppedResult = result ?? {
|
|
614
653
|
text: task.error ?? 'Stopped by TaskStop',
|
|
615
654
|
usage: { inputTokens: 0, outputTokens: 0 },
|
|
@@ -621,7 +660,7 @@ export class BackgroundAgentManager {
|
|
|
621
660
|
if (!task.suppressNotifications) {
|
|
622
661
|
task.notifications.push({
|
|
623
662
|
id: stoppedResult.notificationId ?? randomUUID(),
|
|
624
|
-
status: '
|
|
663
|
+
status: 'cancelled',
|
|
625
664
|
result: stoppedResult,
|
|
626
665
|
error: task.error,
|
|
627
666
|
toolUseId: task.spec.toolUseId,
|
|
@@ -653,11 +692,12 @@ export class BackgroundAgentManager {
|
|
|
653
692
|
}
|
|
654
693
|
formatOutput(task, retrieval) {
|
|
655
694
|
const output = task.result?.text ?? task.error ?? '';
|
|
695
|
+
const status = legacyBackgroundStatus(task.status);
|
|
656
696
|
return [
|
|
657
697
|
`<retrieval_status>${retrieval}</retrieval_status>`,
|
|
658
698
|
`<task_id>${task.spec.agentId}</task_id>`,
|
|
659
699
|
'<task_type>local_agent</task_type>',
|
|
660
|
-
`<status>${
|
|
700
|
+
`<status>${status}</status>`,
|
|
661
701
|
...(task.result?.isolationPath
|
|
662
702
|
? [
|
|
663
703
|
`<worktree_path>${escapeXml(task.result.isolationPath)}</worktree_path>`,
|
|
@@ -674,7 +714,9 @@ export class BackgroundAgentManager {
|
|
|
674
714
|
}
|
|
675
715
|
formatNotification(task, notification) {
|
|
676
716
|
const result = notification.result?.text ?? notification.error ?? '';
|
|
677
|
-
const status = notification.status
|
|
717
|
+
const status = notificationWireStatus(notification.status);
|
|
718
|
+
if (status === null)
|
|
719
|
+
return null;
|
|
678
720
|
const usage = notification.result
|
|
679
721
|
? `<usage><total_tokens>${notification.result.usage.inputTokens + notification.result.usage.outputTokens}</total_tokens><tool_uses>${notification.result.toolUseCount}</tool_uses><duration_ms>${notification.result.durationMs}</duration_ms></usage>`
|
|
680
722
|
: '';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ModelProviderError, type ModelMessage, type ModelToolDefinition, type ModelUsage } from '../core/runtime.js';
|
|
2
|
+
import { type ContextBudget, type ContextBudgetReport } from '../core/context-budget.js';
|
|
3
|
+
export interface ContextEnvelope {
|
|
4
|
+
readonly messages: readonly ModelMessage[];
|
|
5
|
+
readonly tools: readonly ModelToolDefinition[];
|
|
6
|
+
readonly outputTokens?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ContextCompactionProposal {
|
|
9
|
+
readonly envelope: ContextEnvelope;
|
|
10
|
+
commit(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface ContextTransitionInput {
|
|
13
|
+
readonly trigger: 'auto' | 'reactive';
|
|
14
|
+
readonly before: ContextBudgetReport;
|
|
15
|
+
readonly irreducible: ContextBudgetReport;
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
export interface ContextTransitionPort {
|
|
19
|
+
current(): ContextEnvelope;
|
|
20
|
+
irreducible(): ContextEnvelope;
|
|
21
|
+
propose(input: ContextTransitionInput): Promise<ContextCompactionProposal>;
|
|
22
|
+
}
|
|
23
|
+
export interface ContextEngineMemoryPort {
|
|
24
|
+
beforeCompact?(): Promise<void>;
|
|
25
|
+
afterCompact?(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface ContextEngineOptions {
|
|
28
|
+
budget?: ContextBudget;
|
|
29
|
+
memory?: ContextEngineMemoryPort;
|
|
30
|
+
autoCompact?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export type ContextRecoveryResult = {
|
|
33
|
+
readonly kind: 'not-applicable';
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: 'retry';
|
|
36
|
+
readonly envelope: ContextEnvelope;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'exhausted';
|
|
39
|
+
readonly error: ModelProviderError;
|
|
40
|
+
};
|
|
41
|
+
/** Owns context measurement, bounded recovery, and the proposal/commit seam. */
|
|
42
|
+
export declare class ContextEngine {
|
|
43
|
+
private recoveryUsed;
|
|
44
|
+
private readonly budget;
|
|
45
|
+
private readonly memory;
|
|
46
|
+
private readonly autoCompact;
|
|
47
|
+
constructor(options?: ContextEngineOptions);
|
|
48
|
+
prepare(port: ContextTransitionPort, signal?: AbortSignal): Promise<ContextEnvelope>;
|
|
49
|
+
recover(error: unknown, port: ContextTransitionPort, signal?: AbortSignal): Promise<ContextRecoveryResult>;
|
|
50
|
+
observeUsage(usage: ModelUsage, messages: readonly ModelMessage[], tools: readonly ModelToolDefinition[]): void;
|
|
51
|
+
report(envelope: ContextEnvelope, options?: {
|
|
52
|
+
promptTooLong?: boolean;
|
|
53
|
+
}): ContextBudgetReport | undefined;
|
|
54
|
+
private reportRequired;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=context-engine.d.ts.map
|