@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.
@@ -1,8 +1,8 @@
1
1
  import type { AgentActivity } from './plugin-sdk.js';
2
2
  import type { ProviderProgressUpdate } from './types.js';
3
- /** Everything a provider reports. The turn boundary is the reporter's own and never arrives here. */
4
- export type ReportedActivity = Exclude<AgentActivity, {
5
- shape: 'turn' | 'background_run';
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 pump is closed'));
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 || state.backgroundContinuationPending) {
1174
- throw new Error('Claude session scope cannot change while provider-owned background work is running');
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.94",
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.14.1"
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
  }