@borgee/agents-host 0.2.94 → 0.2.97
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/dist/agents-host.js +8 -0
- package/dist/background-runs.d.ts +4 -0
- package/dist/background-runs.js +8 -4
- package/dist/chat/chat-control-plane.d.ts +2 -1
- package/dist/chat/sdk-chat-control-plane.d.ts +3 -2
- package/dist/chat/sdk-chat-control-plane.js +3 -0
- package/dist/plugin-sdk.js +13 -1
- package/dist/plugin-sdk.js.map +2 -2
- package/dist/progress-to-activity.d.ts +3 -3
- package/dist/providers/claude/adapter.d.ts +2 -1
- package/dist/providers/claude/adapter.js +10 -0
- package/dist/providers/claude/cli-client.d.ts +3 -1
- package/dist/providers/claude/cli-client.js +263 -13
- package/package.json +9 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { AgentActivity } from './plugin-sdk.js';
|
|
2
2
|
import type { ProviderProgressUpdate } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
export type ReportedActivity =
|
|
5
|
-
shape: '
|
|
3
|
+
/** Absolute provider state accepted by the foreground activity coalescer. */
|
|
4
|
+
export type ReportedActivity = Extract<AgentActivity, {
|
|
5
|
+
shape: 'activity' | 'plan' | 'output';
|
|
6
6
|
}>;
|
|
7
7
|
/**
|
|
8
8
|
* Restates one progress update in the rail's own vocabulary.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProviderAdapter } from '../provider-adapter.js';
|
|
2
|
-
import type { ProviderGenerateOptions, ProviderInput, ProviderReply } from '../../types.js';
|
|
2
|
+
import type { ProviderAutonomousReply, ProviderGenerateOptions, ProviderInput, ProviderReply } from '../../types.js';
|
|
3
3
|
import { ProviderTurnPreparer } from '../../context/turn-preparation.js';
|
|
4
4
|
import { ClaudeCliClient } from './cli-client.js';
|
|
5
5
|
export declare const CLAUDE_HOSTED_PROVIDER_CAPABILITIES: import("../provider-adapter.js").ProviderCapabilities;
|
|
@@ -10,6 +10,7 @@ export declare class ClaudeProviderAdapter implements ProviderAdapter {
|
|
|
10
10
|
readonly cancelBackgroundRun?: (channelId: string, providerRunId: string) => Promise<boolean>;
|
|
11
11
|
constructor(cli: ClaudeCliClient, turnPreparer: Pick<ProviderTurnPreparer, 'prepare'>);
|
|
12
12
|
generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
|
|
13
|
+
onAutonomousReply(handler: ((reply: ProviderAutonomousReply) => void) | undefined): void;
|
|
13
14
|
cancelTurn(channelId: string): Promise<boolean>;
|
|
14
15
|
dispose(): Promise<void>;
|
|
15
16
|
}
|
|
@@ -60,6 +60,16 @@ export class ClaudeProviderAdapter {
|
|
|
60
60
|
// session as it runs, so before this point there is nothing to report.
|
|
61
61
|
return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.channelId) };
|
|
62
62
|
}
|
|
63
|
+
onAutonomousReply(handler) {
|
|
64
|
+
this.cli.onAutonomousReply(handler
|
|
65
|
+
? (reply) => {
|
|
66
|
+
const parsed = parseProviderReply(reply.text);
|
|
67
|
+
if (parsed.text.trim()) {
|
|
68
|
+
handler({ ...reply, text: parsed.text });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
: undefined);
|
|
72
|
+
}
|
|
63
73
|
async cancelTurn(channelId) {
|
|
64
74
|
return this.cli.cancelTurn(channelId);
|
|
65
75
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
2
|
import { PROTOCOL_VERSION, client, methods, ndJsonStream } from '@agentclientprotocol/sdk';
|
|
3
3
|
import { type DebugLogger } from '../../debug.js';
|
|
4
|
-
import type { PreparedProviderTurnInput, ProviderGenerateOptions } from '../../types.js';
|
|
4
|
+
import type { PreparedProviderTurnInput, ProviderAutonomousReply, ProviderGenerateOptions } from '../../types.js';
|
|
5
5
|
import type { ClaudeChannelSessionStore } from './session-store.js';
|
|
6
6
|
interface ClaudeAcpRuntime {
|
|
7
7
|
spawn: typeof spawn;
|
|
@@ -78,9 +78,11 @@ export declare class ClaudeCliClient {
|
|
|
78
78
|
private sessionStoreWriteQueue;
|
|
79
79
|
private sessionCapabilities;
|
|
80
80
|
private targetedBackgroundRunCancellationAvailable;
|
|
81
|
+
private autonomousReplyHandler?;
|
|
81
82
|
private readonly idleBackendShutdown;
|
|
82
83
|
private childStderr;
|
|
83
84
|
constructor(command: string, args?: string[], runtimeOverrides?: Partial<ClaudeAcpRuntime>, sessionStore?: ClaudeChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, imageInputConfig?: ClaudeHostedImageInputConfig);
|
|
85
|
+
onAutonomousReply(handler: ((reply: ProviderAutonomousReply) => void) | undefined): void;
|
|
84
86
|
generateReply(turn: PreparedProviderTurnInput, options?: ProviderGenerateOptions): Promise<string>;
|
|
85
87
|
generateReply(channelId: string, prompt: string, options?: ProviderGenerateOptions): Promise<string>;
|
|
86
88
|
dispose(): Promise<void>;
|
|
@@ -18,17 +18,26 @@ import { buildClaudeSessionPromptAppend } from '../../context/prompt.js';
|
|
|
18
18
|
import { isDiscussionOnlyResolvedWorkingFolder } from '../../context/resolved-working-folder.js';
|
|
19
19
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
20
20
|
import { CLAUDE_TASK_CANCELLATION_METHOD, supportsClaudeTaskCancellation, } from './task-cancellation-protocol.js';
|
|
21
|
+
const BACKGROUND_COMPLETION_SIGNAL_TTL_MS = 30_000;
|
|
22
|
+
const MAX_BACKGROUND_COMPLETION_SIGNALS = 32;
|
|
23
|
+
const AUTONOMOUS_REPLY_IDLE_FLUSH_MS = 100;
|
|
21
24
|
class ClaudeSessionUpdatePump {
|
|
22
25
|
session;
|
|
23
26
|
onFailure;
|
|
27
|
+
autonomousEvents;
|
|
24
28
|
onForegroundSettledWithBackground;
|
|
25
29
|
observer;
|
|
26
30
|
activeTurn;
|
|
31
|
+
autonomousTurns = new Map();
|
|
32
|
+
backgroundCompletionSignals = [];
|
|
33
|
+
backgroundCompletionSignalExpiry;
|
|
34
|
+
autonomousTurnPending = false;
|
|
27
35
|
closed = false;
|
|
28
36
|
failure;
|
|
29
|
-
constructor(session, onFailure, onBackgroundTasksActiveChanged, onForegroundSettledWithBackground, targetedCancellationSupported) {
|
|
37
|
+
constructor(session, onFailure, onBackgroundTasksActiveChanged, autonomousEvents, onForegroundSettledWithBackground, targetedCancellationSupported) {
|
|
30
38
|
this.session = session;
|
|
31
39
|
this.onFailure = onFailure;
|
|
40
|
+
this.autonomousEvents = autonomousEvents;
|
|
32
41
|
this.onForegroundSettledWithBackground = onForegroundSettledWithBackground;
|
|
33
42
|
this.observer = new ClaudeBackgroundRunObserver(undefined, {
|
|
34
43
|
onBackgroundTasksActiveChanged,
|
|
@@ -49,6 +58,7 @@ class ClaudeSessionUpdatePump {
|
|
|
49
58
|
const turn = {
|
|
50
59
|
queue: [],
|
|
51
60
|
foregroundIsolationFence,
|
|
61
|
+
ownedForegroundTurn: this.resolveForegroundTurn(foregroundIsolationFence),
|
|
52
62
|
};
|
|
53
63
|
this.activeTurn = turn;
|
|
54
64
|
this.observer.setOnProgress(onProgress);
|
|
@@ -85,9 +95,199 @@ class ClaudeSessionUpdatePump {
|
|
|
85
95
|
}
|
|
86
96
|
this.closed = true;
|
|
87
97
|
this.observer.dispose();
|
|
98
|
+
if (this.backgroundCompletionSignalExpiry) {
|
|
99
|
+
clearTimeout(this.backgroundCompletionSignalExpiry);
|
|
100
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
101
|
+
}
|
|
102
|
+
this.backgroundCompletionSignals.length = 0;
|
|
103
|
+
for (const turn of this.autonomousTurns.values()) {
|
|
104
|
+
if (turn.flushTimer) {
|
|
105
|
+
clearTimeout(turn.flushTimer);
|
|
106
|
+
}
|
|
107
|
+
if (turn.discardTimer) {
|
|
108
|
+
clearTimeout(turn.discardTimer);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.autonomousTurns.clear();
|
|
112
|
+
this.refreshAutonomousTurnPresence();
|
|
88
113
|
const turn = this.activeTurn;
|
|
89
114
|
this.activeTurn = undefined;
|
|
90
|
-
turn?.waiter?.reject(new Error('Claude ACP session update
|
|
115
|
+
turn?.waiter?.reject(new Error('Claude ACP session update stream is closed'));
|
|
116
|
+
}
|
|
117
|
+
resolveForegroundTurn(foregroundIsolationFence) {
|
|
118
|
+
const nextForegroundTurn = foregroundIsolationFence === undefined ? 1 : foregroundIsolationFence + 1;
|
|
119
|
+
return Number.isSafeInteger(nextForegroundTurn) && nextForegroundTurn > 0
|
|
120
|
+
? nextForegroundTurn
|
|
121
|
+
: undefined;
|
|
122
|
+
}
|
|
123
|
+
autonomousTurnFor(foregroundTurn) {
|
|
124
|
+
let turn = this.autonomousTurns.get(foregroundTurn);
|
|
125
|
+
if (turn) {
|
|
126
|
+
return turn;
|
|
127
|
+
}
|
|
128
|
+
turn = {
|
|
129
|
+
eventId: `claude-background-${foregroundTurn}`,
|
|
130
|
+
confirmed: false,
|
|
131
|
+
text: '',
|
|
132
|
+
};
|
|
133
|
+
this.autonomousTurns.set(foregroundTurn, turn);
|
|
134
|
+
return turn;
|
|
135
|
+
}
|
|
136
|
+
noteBackgroundCompletionSignal(foregroundTurn, eventId) {
|
|
137
|
+
const turn = this.autonomousTurnFor(foregroundTurn);
|
|
138
|
+
if (turn.discardTimer) {
|
|
139
|
+
clearTimeout(turn.discardTimer);
|
|
140
|
+
turn.discardTimer = undefined;
|
|
141
|
+
}
|
|
142
|
+
this.pruneBackgroundCompletionSignals();
|
|
143
|
+
const existingIndex = this.backgroundCompletionSignals.findIndex((signal) => signal.foregroundTurn === foregroundTurn);
|
|
144
|
+
if (existingIndex >= 0) {
|
|
145
|
+
this.backgroundCompletionSignals.splice(existingIndex, 1);
|
|
146
|
+
}
|
|
147
|
+
this.backgroundCompletionSignals.push({
|
|
148
|
+
eventId,
|
|
149
|
+
foregroundTurn,
|
|
150
|
+
expiresAt: Date.now() + BACKGROUND_COMPLETION_SIGNAL_TTL_MS,
|
|
151
|
+
});
|
|
152
|
+
if (this.backgroundCompletionSignals.length > MAX_BACKGROUND_COMPLETION_SIGNALS) {
|
|
153
|
+
this.backgroundCompletionSignals.splice(0, this.backgroundCompletionSignals.length - MAX_BACKGROUND_COMPLETION_SIGNALS);
|
|
154
|
+
}
|
|
155
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
156
|
+
turn.confirmed = true;
|
|
157
|
+
turn.eventId = eventId;
|
|
158
|
+
if (turn.text.trim()) {
|
|
159
|
+
this.scheduleAutonomousReplyFlush(foregroundTurn, turn);
|
|
160
|
+
}
|
|
161
|
+
this.refreshAutonomousTurnPresence();
|
|
162
|
+
return turn;
|
|
163
|
+
}
|
|
164
|
+
consumeAutonomousLifecycle(update) {
|
|
165
|
+
if (update.update.sessionUpdate !== 'session_info_update') {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const provider = asObject(update.update._meta)?.claudeCode;
|
|
169
|
+
const lifecycle = asObject(asObject(provider)?.taskLifecycle);
|
|
170
|
+
if (lifecycle?.event !== 'notification' || lifecycle?.status !== 'completed') {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const foregroundTurn = asObject(provider)?.foregroundTurn;
|
|
174
|
+
if (!Number.isSafeInteger(foregroundTurn) || foregroundTurn <= 0) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const taskId = typeof lifecycle.taskId === 'string' ? lifecycle.taskId : 'task';
|
|
178
|
+
this.noteBackgroundCompletionSignal(foregroundTurn, `${taskId}:completed`);
|
|
179
|
+
}
|
|
180
|
+
consumeAutonomousUpdate(update) {
|
|
181
|
+
if (update.update.sessionUpdate !== 'agent_message_chunk' || update.update.content.type !== 'text') {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const provider = asObject(update.update._meta)?.claudeCode;
|
|
185
|
+
const foregroundTurn = asObject(provider)?.foregroundTurn;
|
|
186
|
+
if (!Number.isSafeInteger(foregroundTurn) || foregroundTurn <= 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const lane = asObject(provider)?.foregroundLane;
|
|
190
|
+
const activeForegroundTurn = this.activeTurn?.ownedForegroundTurn;
|
|
191
|
+
if (foregroundTurn === activeForegroundTurn) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (lane !== 'background' && !this.backgroundCompletionSignals.some((signal) => signal.foregroundTurn === foregroundTurn)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const turn = this.autonomousTurnFor(foregroundTurn);
|
|
198
|
+
turn.text += update.update.content.text;
|
|
199
|
+
if (turn.confirmed) {
|
|
200
|
+
this.scheduleAutonomousReplyFlush(foregroundTurn, turn);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
scheduleAutonomousReplyFlush(foregroundTurn, turn) {
|
|
204
|
+
if (turn.flushTimer) {
|
|
205
|
+
clearTimeout(turn.flushTimer);
|
|
206
|
+
}
|
|
207
|
+
turn.flushTimer = setTimeout(() => {
|
|
208
|
+
turn.flushTimer = undefined;
|
|
209
|
+
this.flushAutonomousReply(foregroundTurn, turn);
|
|
210
|
+
}, AUTONOMOUS_REPLY_IDLE_FLUSH_MS);
|
|
211
|
+
turn.flushTimer.unref?.();
|
|
212
|
+
}
|
|
213
|
+
flushAutonomousReply(foregroundTurn, turn) {
|
|
214
|
+
const text = turn.text.trim();
|
|
215
|
+
if (turn.confirmed && text) {
|
|
216
|
+
this.autonomousEvents.onReply?.({
|
|
217
|
+
eventId: turn.eventId,
|
|
218
|
+
text,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else if (turn.confirmed) {
|
|
222
|
+
turn.discardTimer = setTimeout(() => {
|
|
223
|
+
turn.discardTimer = undefined;
|
|
224
|
+
this.discardAutonomousTurn(foregroundTurn, turn);
|
|
225
|
+
}, BACKGROUND_COMPLETION_SIGNAL_TTL_MS);
|
|
226
|
+
turn.discardTimer.unref?.();
|
|
227
|
+
this.refreshAutonomousTurnPresence();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.discardAutonomousTurn(foregroundTurn, turn);
|
|
231
|
+
}
|
|
232
|
+
discardAutonomousTurn(foregroundTurn, turn) {
|
|
233
|
+
if (turn.flushTimer) {
|
|
234
|
+
clearTimeout(turn.flushTimer);
|
|
235
|
+
turn.flushTimer = undefined;
|
|
236
|
+
}
|
|
237
|
+
if (turn.discardTimer) {
|
|
238
|
+
clearTimeout(turn.discardTimer);
|
|
239
|
+
turn.discardTimer = undefined;
|
|
240
|
+
}
|
|
241
|
+
if (this.autonomousTurns.get(foregroundTurn) === turn) {
|
|
242
|
+
this.autonomousTurns.delete(foregroundTurn);
|
|
243
|
+
}
|
|
244
|
+
const signalIndex = this.backgroundCompletionSignals.findIndex((signal) => signal.foregroundTurn === foregroundTurn);
|
|
245
|
+
if (signalIndex >= 0) {
|
|
246
|
+
this.backgroundCompletionSignals.splice(signalIndex, 1);
|
|
247
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.refreshAutonomousTurnPresence();
|
|
251
|
+
}
|
|
252
|
+
pruneBackgroundCompletionSignals() {
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
while (this.backgroundCompletionSignals.length > 0
|
|
255
|
+
&& this.backgroundCompletionSignals[0].expiresAt <= now) {
|
|
256
|
+
const expired = this.backgroundCompletionSignals.shift();
|
|
257
|
+
if (!expired) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const turn = this.autonomousTurns.get(expired.foregroundTurn);
|
|
261
|
+
if (turn && !turn.text.trim()) {
|
|
262
|
+
this.discardAutonomousTurn(expired.foregroundTurn, turn);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
scheduleBackgroundCompletionSignalExpiry() {
|
|
267
|
+
if (this.backgroundCompletionSignalExpiry) {
|
|
268
|
+
clearTimeout(this.backgroundCompletionSignalExpiry);
|
|
269
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
270
|
+
}
|
|
271
|
+
this.pruneBackgroundCompletionSignals();
|
|
272
|
+
const next = this.backgroundCompletionSignals[0];
|
|
273
|
+
if (!next) {
|
|
274
|
+
this.refreshAutonomousTurnPresence();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.backgroundCompletionSignalExpiry = setTimeout(() => {
|
|
278
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
279
|
+
this.pruneBackgroundCompletionSignals();
|
|
280
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
281
|
+
}, Math.max(0, next.expiresAt - Date.now()));
|
|
282
|
+
this.backgroundCompletionSignalExpiry.unref?.();
|
|
283
|
+
}
|
|
284
|
+
refreshAutonomousTurnPresence() {
|
|
285
|
+
const pending = this.autonomousTurns.size > 0 || this.backgroundCompletionSignals.length > 0;
|
|
286
|
+
if (pending === this.autonomousTurnPending) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.autonomousTurnPending = pending;
|
|
290
|
+
this.autonomousEvents.onPendingChanged?.(pending);
|
|
91
291
|
}
|
|
92
292
|
async run() {
|
|
93
293
|
try {
|
|
@@ -98,8 +298,12 @@ class ClaudeSessionUpdatePump {
|
|
|
98
298
|
}
|
|
99
299
|
const lifecycle = update.kind === 'session_update' && this.observer.consume(update.update);
|
|
100
300
|
if (lifecycle) {
|
|
301
|
+
this.consumeAutonomousLifecycle(update);
|
|
101
302
|
continue;
|
|
102
303
|
}
|
|
304
|
+
if (update.kind === 'session_update') {
|
|
305
|
+
this.consumeAutonomousUpdate(update);
|
|
306
|
+
}
|
|
103
307
|
if (update.kind === 'session_update'
|
|
104
308
|
&& isClaudeUpdateFromIsolatedTurn(update.update, this.activeTurn?.foregroundIsolationFence)) {
|
|
105
309
|
continue;
|
|
@@ -731,6 +935,7 @@ export class ClaudeCliClient {
|
|
|
731
935
|
sessionStoreWriteQueue = Promise.resolve();
|
|
732
936
|
sessionCapabilities = DEFAULT_SESSION_CAPABILITIES;
|
|
733
937
|
targetedBackgroundRunCancellationAvailable = false;
|
|
938
|
+
autonomousReplyHandler;
|
|
734
939
|
idleBackendShutdown;
|
|
735
940
|
childStderr = '';
|
|
736
941
|
constructor(command, args = [], runtimeOverrides = {}, sessionStore, resolveSessionStoreAgentId = () => undefined, logger = new HostLogger(), imageInputConfig = {}) {
|
|
@@ -759,6 +964,9 @@ export class ClaudeCliClient {
|
|
|
759
964
|
});
|
|
760
965
|
void this.fatalPromise.catch(() => { });
|
|
761
966
|
}
|
|
967
|
+
onAutonomousReply(handler) {
|
|
968
|
+
this.autonomousReplyHandler = handler;
|
|
969
|
+
}
|
|
762
970
|
async generateReply(channelIdOrTurn, promptOrOptions, maybeOptions) {
|
|
763
971
|
if (this.fatalError) {
|
|
764
972
|
throw this.fatalError;
|
|
@@ -790,6 +998,7 @@ export class ClaudeCliClient {
|
|
|
790
998
|
async dispose() {
|
|
791
999
|
const error = new Error('Claude ACP backend stopped');
|
|
792
1000
|
this.disposing = true;
|
|
1001
|
+
this.autonomousReplyHandler = undefined;
|
|
793
1002
|
this.idleBackendShutdown.cancel();
|
|
794
1003
|
this.logger?.debug('stopping Claude ACP backend');
|
|
795
1004
|
const closed = this.connection?.closed ?? Promise.resolve();
|
|
@@ -936,6 +1145,7 @@ export class ClaudeCliClient {
|
|
|
936
1145
|
sessionPersistence,
|
|
937
1146
|
backgroundTasksActive: false,
|
|
938
1147
|
backgroundContinuationPending: false,
|
|
1148
|
+
sessionHasPendingAutonomousTurn: false,
|
|
939
1149
|
persistentFollowUpAdopted: false,
|
|
940
1150
|
processing: false,
|
|
941
1151
|
queue: [],
|
|
@@ -1170,8 +1380,10 @@ export class ClaudeCliClient {
|
|
|
1170
1380
|
&& state.sessionVisibilityKey === state.visibilityKey) {
|
|
1171
1381
|
return;
|
|
1172
1382
|
}
|
|
1173
|
-
if (state.backgroundTasksActive
|
|
1174
|
-
|
|
1383
|
+
if (state.backgroundTasksActive
|
|
1384
|
+
|| state.backgroundContinuationPending
|
|
1385
|
+
|| state.sessionHasPendingAutonomousTurn) {
|
|
1386
|
+
throw new Error('Claude session scope cannot change while provider-owned background delivery is running');
|
|
1175
1387
|
}
|
|
1176
1388
|
this.logger?.debug('recycling Claude ACP session after session scope changed', {
|
|
1177
1389
|
channelId,
|
|
@@ -1183,6 +1395,7 @@ export class ClaudeCliClient {
|
|
|
1183
1395
|
state.session = undefined;
|
|
1184
1396
|
state.backgroundTasksActive = false;
|
|
1185
1397
|
state.backgroundContinuationPending = false;
|
|
1398
|
+
state.sessionHasPendingAutonomousTurn = false;
|
|
1186
1399
|
state.foregroundIsolationFence = undefined;
|
|
1187
1400
|
state.sessionCwd = undefined;
|
|
1188
1401
|
state.sessionVisibilityKey = undefined;
|
|
@@ -1219,6 +1432,40 @@ export class ClaudeCliClient {
|
|
|
1219
1432
|
}
|
|
1220
1433
|
this.reconcileIdleChannelState(state);
|
|
1221
1434
|
this.idleBackendShutdown.reconcile();
|
|
1435
|
+
}, {
|
|
1436
|
+
onReply: (reply) => {
|
|
1437
|
+
const binding = [...this.channels.entries()].find(([, candidate]) => candidate.session === session);
|
|
1438
|
+
if (!binding) {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const [, state] = binding;
|
|
1442
|
+
if (state.session !== session) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
this.autonomousReplyHandler?.({
|
|
1446
|
+
channelId: state.channelId,
|
|
1447
|
+
eventId: reply.eventId,
|
|
1448
|
+
text: reply.text,
|
|
1449
|
+
});
|
|
1450
|
+
},
|
|
1451
|
+
onPendingChanged: (active) => {
|
|
1452
|
+
const binding = [...this.channels.entries()].find(([, candidate]) => candidate.session === session);
|
|
1453
|
+
if (!binding) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
const [, state] = binding;
|
|
1457
|
+
if (state.session !== session) {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
state.sessionHasPendingAutonomousTurn = active;
|
|
1461
|
+
if (active) {
|
|
1462
|
+
this.promoteWorkerRoute(state, session);
|
|
1463
|
+
this.idleBackendShutdown.cancel();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
this.reconcileIdleChannelState(state);
|
|
1467
|
+
this.idleBackendShutdown.reconcile();
|
|
1468
|
+
},
|
|
1222
1469
|
}, (foregroundTurn) => {
|
|
1223
1470
|
const binding = [...this.channels.entries()].find(([, candidate]) => candidate.session === session);
|
|
1224
1471
|
if (!binding) {
|
|
@@ -1270,12 +1517,6 @@ export class ClaudeCliClient {
|
|
|
1270
1517
|
}
|
|
1271
1518
|
return output;
|
|
1272
1519
|
}
|
|
1273
|
-
const settledForegroundTurn = update.kind === 'session_update'
|
|
1274
|
-
? readClaudeForegroundSettledTurn(update.update)
|
|
1275
|
-
: undefined;
|
|
1276
|
-
if (settledForegroundTurn !== undefined) {
|
|
1277
|
-
return collector.getFinalText();
|
|
1278
|
-
}
|
|
1279
1520
|
if (update.update.sessionUpdate === 'agent_message_chunk' &&
|
|
1280
1521
|
update.update.content.type === 'text') {
|
|
1281
1522
|
compactionObserver.consumeAnswerChunk(update.update.content.text);
|
|
@@ -1315,11 +1556,13 @@ export class ClaudeCliClient {
|
|
|
1315
1556
|
state.session = undefined;
|
|
1316
1557
|
state.backgroundTasksActive = false;
|
|
1317
1558
|
state.backgroundContinuationPending = false;
|
|
1559
|
+
state.sessionHasPendingAutonomousTurn = false;
|
|
1318
1560
|
state.foregroundIsolationFence = undefined;
|
|
1319
1561
|
state.sessionCwd = undefined;
|
|
1320
1562
|
state.sessionVisibilityKey = undefined;
|
|
1321
1563
|
state.sessionPromise = undefined;
|
|
1322
1564
|
state.activeTurn = undefined;
|
|
1565
|
+
state.persistentFollowUpAdopted = false;
|
|
1323
1566
|
if (!state.processing) {
|
|
1324
1567
|
this.channels.delete(channelId);
|
|
1325
1568
|
}
|
|
@@ -1346,9 +1589,11 @@ export class ClaudeCliClient {
|
|
|
1346
1589
|
state.session = undefined;
|
|
1347
1590
|
state.backgroundTasksActive = false;
|
|
1348
1591
|
state.backgroundContinuationPending = false;
|
|
1592
|
+
state.sessionHasPendingAutonomousTurn = false;
|
|
1349
1593
|
state.foregroundIsolationFence = undefined;
|
|
1350
1594
|
state.sessionCwd = undefined;
|
|
1351
1595
|
state.sessionVisibilityKey = undefined;
|
|
1596
|
+
state.persistentFollowUpAdopted = false;
|
|
1352
1597
|
}
|
|
1353
1598
|
this.logger?.debug('discarding tainted Claude ACP session', { channelId });
|
|
1354
1599
|
this.closeSession(session);
|
|
@@ -1370,7 +1615,7 @@ export class ClaudeCliClient {
|
|
|
1370
1615
|
if (route?.state !== state
|
|
1371
1616
|
|| route.phase !== 'promoted'
|
|
1372
1617
|
|| state.session !== session
|
|
1373
|
-
|| (!state.backgroundTasksActive && !state.persistentFollowUpAdopted)) {
|
|
1618
|
+
|| (!state.backgroundTasksActive && !state.sessionHasPendingAutonomousTurn && !state.persistentFollowUpAdopted)) {
|
|
1374
1619
|
return;
|
|
1375
1620
|
}
|
|
1376
1621
|
await this.persistSessionBestEffort(state.channelId, session.sessionId, state.visibilityKey, state.sessionCwd ?? state.cwd ?? this.runtime.cwd);
|
|
@@ -1408,7 +1653,8 @@ export class ClaudeCliClient {
|
|
|
1408
1653
|
if (route?.state === state
|
|
1409
1654
|
&& route.phase === 'promoted'
|
|
1410
1655
|
&& !state.backgroundTasksActive
|
|
1411
|
-
&& !state.backgroundContinuationPending
|
|
1656
|
+
&& !state.backgroundContinuationPending
|
|
1657
|
+
&& !state.sessionHasPendingAutonomousTurn) {
|
|
1412
1658
|
if (state.routingKey === state.channelId) {
|
|
1413
1659
|
this.releaseWorkerRoute(state);
|
|
1414
1660
|
}
|
|
@@ -1425,7 +1671,8 @@ export class ClaudeCliClient {
|
|
|
1425
1671
|
}
|
|
1426
1672
|
if (state.sessionPersistence !== 'ephemeral'
|
|
1427
1673
|
|| state.backgroundTasksActive
|
|
1428
|
-
|| state.backgroundContinuationPending
|
|
1674
|
+
|| state.backgroundContinuationPending
|
|
1675
|
+
|| state.sessionHasPendingAutonomousTurn) {
|
|
1429
1676
|
return;
|
|
1430
1677
|
}
|
|
1431
1678
|
const session = state.session;
|
|
@@ -1633,9 +1880,11 @@ export class ClaudeCliClient {
|
|
|
1633
1880
|
state.session = undefined;
|
|
1634
1881
|
state.backgroundTasksActive = false;
|
|
1635
1882
|
state.backgroundContinuationPending = false;
|
|
1883
|
+
state.sessionHasPendingAutonomousTurn = false;
|
|
1636
1884
|
state.foregroundIsolationFence = undefined;
|
|
1637
1885
|
state.sessionCwd = undefined;
|
|
1638
1886
|
state.sessionVisibilityKey = undefined;
|
|
1887
|
+
state.persistentFollowUpAdopted = false;
|
|
1639
1888
|
this.channels.delete(channelId);
|
|
1640
1889
|
}
|
|
1641
1890
|
this.channelWorkerRoutes.clear();
|
|
@@ -1670,6 +1919,7 @@ export class ClaudeCliClient {
|
|
|
1670
1919
|
if (state.activeTurn ||
|
|
1671
1920
|
state.backgroundTasksActive ||
|
|
1672
1921
|
state.backgroundContinuationPending ||
|
|
1922
|
+
state.sessionHasPendingAutonomousTurn ||
|
|
1673
1923
|
state.queue.length > 0 ||
|
|
1674
1924
|
state.sessionPromise ||
|
|
1675
1925
|
state.processing) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgee/agents-host",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.97",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -38,24 +38,24 @@
|
|
|
38
38
|
"tsx": "^4.20.5",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
40
|
"vitest": "^4.1.5",
|
|
41
|
-
"@borgee/plugin-sdk": "0.
|
|
41
|
+
"@borgee/plugin-sdk": "0.15.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"predev": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
44
|
+
"predev": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
45
45
|
"dev": "tsx src/index.ts",
|
|
46
|
-
"precli": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
46
|
+
"precli": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
47
47
|
"cli": "tsx src/cli.ts",
|
|
48
|
-
"prestart": "pnpm --filter @borgee/plugin-sdk build",
|
|
48
|
+
"prestart": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
49
49
|
"start": "node dist/index.js",
|
|
50
|
-
"prebuild": "pnpm --filter @borgee/plugin-sdk build",
|
|
50
|
+
"prebuild": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
51
51
|
"build": "tsc && node scripts/bundle-plugin-sdk.mjs && node scripts/vendor-claude-acp.mjs",
|
|
52
52
|
"test:package": "node --test --test-timeout=10000 scripts/packed-package-descriptor.test.mjs && pnpm run build && node scripts/test-packed-claude-lifecycle.mjs",
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
|
-
"pretest": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
54
|
+
"pretest": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
55
55
|
"test": "vitest run --testTimeout=10000",
|
|
56
|
-
"pretest:e2e": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
56
|
+
"pretest:e2e": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
57
57
|
"test:e2e": "vitest run --config vitest.e2e.config.ts --testTimeout=120000",
|
|
58
|
-
"pretypecheck": "pnpm --filter @borgee/plugin-sdk build",
|
|
58
|
+
"pretypecheck": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
59
59
|
"vendor:claude": "node scripts/vendor-claude-acp.mjs"
|
|
60
60
|
}
|
|
61
61
|
}
|