praxis-agent 0.67.1 → 0.67.2
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.
|
@@ -901,7 +901,7 @@ export class ClaudeSessionService {
|
|
|
901
901
|
}
|
|
902
902
|
async close() {
|
|
903
903
|
this.closing = true;
|
|
904
|
-
this.turnCoordinator.close();
|
|
904
|
+
await this.turnCoordinator.close();
|
|
905
905
|
await this.fileChangeWatcher?.close(5_000);
|
|
906
906
|
await this.hookLifecycle.close();
|
|
907
907
|
await this.drainDetachedHookRuns(5_000);
|
|
@@ -2590,7 +2590,7 @@ export class ClaudeSessionService {
|
|
|
2590
2590
|
return tracker.snapshot();
|
|
2591
2591
|
}
|
|
2592
2592
|
async executeTurn(request) {
|
|
2593
|
-
const { activation, submission
|
|
2593
|
+
const { activation, submission } = request;
|
|
2594
2594
|
const sessionId = activation.sessionId;
|
|
2595
2595
|
const requireExisting = activation.kind === 'resume';
|
|
2596
2596
|
const name = activation.name;
|
|
@@ -2604,7 +2604,8 @@ export class ClaudeSessionService {
|
|
|
2604
2604
|
const documents = submission.kind === 'prompt' ? (submission.documents ?? []) : [];
|
|
2605
2605
|
const shellCommand = submission.kind === 'shell' ? submission.command : undefined;
|
|
2606
2606
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2607
|
-
return this.turnCoordinator.run(request, async (
|
|
2607
|
+
return this.turnCoordinator.run(request, async (scope) => {
|
|
2608
|
+
const { emit, signal, steering } = scope;
|
|
2608
2609
|
this.assertTurnWritable();
|
|
2609
2610
|
await this.activateSessionCostTracker(sessionId);
|
|
2610
2611
|
await this.ensureFileResources(sessionId, signal);
|
|
@@ -29,6 +29,7 @@ export interface TurnRequest {
|
|
|
29
29
|
}
|
|
30
30
|
export interface TurnScope {
|
|
31
31
|
readonly emit: RuntimeEventSink;
|
|
32
|
+
readonly signal: AbortSignal;
|
|
32
33
|
readonly steering?: ActiveTurnInputPort;
|
|
33
34
|
}
|
|
34
35
|
export interface TurnCoordinatorOptions {
|
|
@@ -39,11 +40,13 @@ export interface TurnCoordinatorOptions {
|
|
|
39
40
|
export declare class TurnCoordinator {
|
|
40
41
|
private readonly options;
|
|
41
42
|
private readonly activeTurns;
|
|
43
|
+
private closing;
|
|
44
|
+
private closePromise;
|
|
42
45
|
constructor(options: TurnCoordinatorOptions);
|
|
43
46
|
run<T>(request: TurnRequest, work: (scope: TurnScope) => Promise<T>): Promise<T>;
|
|
44
47
|
steer(sessionId: string, content: string): ActiveTurnInputCommandResult;
|
|
45
48
|
withdrawSteering(sessionId: string, id: string): ActiveTurnInputCommandResult;
|
|
46
|
-
close(): void
|
|
49
|
+
close(): Promise<void>;
|
|
47
50
|
private validateRequest;
|
|
48
51
|
private terminalState;
|
|
49
52
|
private transition;
|
|
@@ -4,6 +4,8 @@ import { ActiveTurnInputMailbox, } from '../core/active-turn-input.js';
|
|
|
4
4
|
export class TurnCoordinator {
|
|
5
5
|
options;
|
|
6
6
|
activeTurns = new Map();
|
|
7
|
+
closing = false;
|
|
8
|
+
closePromise;
|
|
7
9
|
constructor(options) {
|
|
8
10
|
this.options = options;
|
|
9
11
|
}
|
|
@@ -12,13 +14,23 @@ export class TurnCoordinator {
|
|
|
12
14
|
const mailbox = request.submission.kind === 'shell'
|
|
13
15
|
? undefined
|
|
14
16
|
: new ActiveTurnInputMailbox(this.options.createSteeringId);
|
|
17
|
+
let settle;
|
|
18
|
+
const settled = new Promise((resolve) => {
|
|
19
|
+
settle = resolve;
|
|
20
|
+
});
|
|
21
|
+
const controller = new AbortController();
|
|
15
22
|
const record = {
|
|
16
23
|
...(mailbox ? { mailbox } : {}),
|
|
24
|
+
controller,
|
|
25
|
+
settled,
|
|
26
|
+
settle,
|
|
17
27
|
terminal: false,
|
|
18
28
|
};
|
|
19
29
|
let terminalState = 'failed';
|
|
20
30
|
let pendingFailure;
|
|
31
|
+
let callerAbort;
|
|
21
32
|
const scope = {
|
|
33
|
+
signal: controller.signal,
|
|
22
34
|
emit: (event) => {
|
|
23
35
|
if (event.type === 'state' &&
|
|
24
36
|
(event.state === 'completed' ||
|
|
@@ -31,19 +43,29 @@ export class TurnCoordinator {
|
|
|
31
43
|
...(mailbox ? { steering: mailbox } : {}),
|
|
32
44
|
};
|
|
33
45
|
try {
|
|
46
|
+
if (request.signal?.aborted)
|
|
47
|
+
controller.abort(request.signal.reason);
|
|
34
48
|
this.validateRequest(request);
|
|
35
49
|
if (this.activeTurns.has(sessionId)) {
|
|
36
50
|
throw new Error(`conflict: locked (session ${sessionId} already has an active turn)`);
|
|
37
51
|
}
|
|
52
|
+
if (this.closing)
|
|
53
|
+
throw new Error('turn coordinator is closed');
|
|
38
54
|
this.activeTurns.set(sessionId, record);
|
|
55
|
+
if (request.signal && !request.signal.aborted) {
|
|
56
|
+
callerAbort = () => controller.abort(request.signal?.reason);
|
|
57
|
+
request.signal.addEventListener('abort', callerAbort, { once: true });
|
|
58
|
+
}
|
|
39
59
|
const result = await work(scope);
|
|
60
|
+
if (controller.signal.aborted)
|
|
61
|
+
throw new AgentRunCancelledError();
|
|
40
62
|
terminalState = 'completed';
|
|
41
63
|
this.transition(record, 'completed');
|
|
42
64
|
return result;
|
|
43
65
|
}
|
|
44
66
|
catch (error) {
|
|
45
67
|
if (!record.terminal) {
|
|
46
|
-
terminalState = this.terminalState(error,
|
|
68
|
+
terminalState = this.terminalState(error, controller.signal);
|
|
47
69
|
this.transition(record, terminalState);
|
|
48
70
|
}
|
|
49
71
|
throw error;
|
|
@@ -55,10 +77,14 @@ export class TurnCoordinator {
|
|
|
55
77
|
}
|
|
56
78
|
}
|
|
57
79
|
finally {
|
|
80
|
+
if (callerAbort && request.signal) {
|
|
81
|
+
request.signal.removeEventListener('abort', callerAbort);
|
|
82
|
+
}
|
|
58
83
|
if (this.activeTurns.get(sessionId) === record) {
|
|
59
84
|
this.activeTurns.delete(sessionId);
|
|
60
85
|
}
|
|
61
86
|
}
|
|
87
|
+
record.settle();
|
|
62
88
|
if (pendingFailure) {
|
|
63
89
|
// A rejected-input sink failure intentionally retains its prior precedence.
|
|
64
90
|
// eslint-disable-next-line no-unsafe-finally -- compatibility is covered by the sink-error regression
|
|
@@ -86,16 +112,33 @@ export class TurnCoordinator {
|
|
|
86
112
|
const result = active.mailbox.withdraw(id);
|
|
87
113
|
return result.kind === 'withdrawn' ? result : { kind: 'not-pending' };
|
|
88
114
|
}
|
|
89
|
-
close() {
|
|
115
|
+
async close() {
|
|
116
|
+
if (this.closePromise)
|
|
117
|
+
return this.closePromise;
|
|
118
|
+
this.closing = true;
|
|
119
|
+
const snapshot = [...this.activeTurns.values()];
|
|
90
120
|
let firstFailure;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
121
|
+
let resolveClose;
|
|
122
|
+
let rejectClose;
|
|
123
|
+
this.closePromise = new Promise((resolve, reject) => {
|
|
124
|
+
resolveClose = resolve;
|
|
125
|
+
rejectClose = reject;
|
|
126
|
+
});
|
|
127
|
+
void (async () => {
|
|
128
|
+
for (const active of snapshot) {
|
|
129
|
+
if (active.mailbox) {
|
|
130
|
+
const failure = this.rejectPending(active.mailbox.close(), 'closed');
|
|
131
|
+
firstFailure ??= failure;
|
|
132
|
+
}
|
|
133
|
+
active.controller.abort();
|
|
134
|
+
}
|
|
135
|
+
await Promise.allSettled(snapshot.map((active) => active.settled));
|
|
136
|
+
if (firstFailure)
|
|
137
|
+
rejectClose(firstFailure.error);
|
|
138
|
+
else
|
|
139
|
+
resolveClose();
|
|
140
|
+
})();
|
|
141
|
+
return this.closePromise;
|
|
99
142
|
}
|
|
100
143
|
validateRequest(request) {
|
|
101
144
|
const { activation, submission } = request;
|
package/dist/build-identity.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schema_version":"1.0","source_revision":"git:
|
|
1
|
+
{"schema_version":"1.0","source_revision":"git:cdfb04df03516521ee1f366319665ef07c41fdb7","source_dirty":false,"artifact_sha256":"sha256:506df1f26b80d8092b1748de2a1130ad9b4b9fa423776d31f911d81cd5976610"}
|