@borgee/agents-host 0.2.62 → 0.2.68

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.
@@ -9,5 +9,6 @@ export declare class ClaudeProviderAdapter implements ProviderAdapter {
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
10
  constructor(cli: ClaudeCliClient, turnPreparer: ProviderTurnPreparer);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
+ cancelTurn(channelId: string): Promise<boolean>;
12
13
  dispose(): Promise<void>;
13
14
  }
@@ -25,6 +25,9 @@ export class ClaudeProviderAdapter {
25
25
  // session as it runs, so before this point there is nothing to report.
26
26
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.channelId) };
27
27
  }
28
+ async cancelTurn(channelId) {
29
+ return this.cli.cancelTurn(channelId);
30
+ }
28
31
  async dispose() {
29
32
  await this.cli.dispose();
30
33
  }
@@ -43,6 +43,7 @@ export declare class ClaudeCliClient {
43
43
  * worked a task without the agent being asked to report its own id.
44
44
  */
45
45
  sessionIdForChannel(channelId: string): string | undefined;
46
+ cancelTurn(channelId: string): Promise<boolean>;
46
47
  private readonly persistedSessions;
47
48
  private readonly closingSessions;
48
49
  private readonly pendingSessionStarts;
@@ -9,6 +9,7 @@ import { assertClaudeCommandCompatibility, DEFAULT_CLAUDE_COMMAND, isLegacyClaud
9
9
  import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
10
10
  import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
11
11
  import { AcpProgressCollector } from '../acp-progress-collector.js';
12
+ import { ProviderTurnCancelledError } from '../provider-adapter.js';
12
13
  import { readClaudeActivityMetadata } from './activity-metadata.js';
13
14
  import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
14
15
  import { buildClaudeSessionPromptAppend } from '../../context/prompt.js';
@@ -32,6 +33,7 @@ const DEFAULT_SESSION_CAPABILITIES = {
32
33
  };
33
34
  const CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD_ENV = 'CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD';
34
35
  const require = createRequire(import.meta.url);
36
+ const CLAUDE_CANCELLED_REPLY_TEXT = 'Info: Operation cancelled by user';
35
37
  const DEFAULT_RUNTIME = {
36
38
  spawn,
37
39
  client,
@@ -495,6 +497,22 @@ export class ClaudeCliClient {
495
497
  sessionIdForChannel(channelId) {
496
498
  return this.channels.get(channelId)?.session?.sessionId;
497
499
  }
500
+ async cancelTurn(channelId) {
501
+ const state = this.channels.get(channelId);
502
+ const session = state?.session;
503
+ if (!state?.activeTurn || !session) {
504
+ return false;
505
+ }
506
+ this.idleBackendShutdown.cancel();
507
+ const activeSession = session;
508
+ if (typeof activeSession.cancel === 'function') {
509
+ await activeSession.cancel({ sessionId: session.sessionId });
510
+ return true;
511
+ }
512
+ const cancelMethod = this.runtime.methods.agent.session?.cancel ?? 'session/cancel';
513
+ await this.connection?.agent.notify(cancelMethod, { sessionId: session.sessionId });
514
+ return true;
515
+ }
498
516
  persistedSessions = new Map();
499
517
  closingSessions = new WeakSet();
500
518
  pendingSessionStarts = new Set();
@@ -975,6 +993,11 @@ export class ClaudeCliClient {
975
993
  throw markSessionTainted(error);
976
994
  }
977
995
  const output = collector.getFinalText();
996
+ if (response.stopReason === 'cancelled') {
997
+ throw new ProviderTurnCancelledError('Claude ACP turn cancelled', {
998
+ publicReplyText: hasVisibleText(output) ? output : CLAUDE_CANCELLED_REPLY_TEXT,
999
+ });
1000
+ }
978
1001
  if (response.stopReason !== 'end_turn' && !hasVisibleText(output)) {
979
1002
  throw new Error(`Claude ACP turn stopped with stopReason "${response.stopReason}"`);
980
1003
  }
@@ -9,5 +9,6 @@ export declare class CodexProviderAdapter implements ProviderAdapter {
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
10
  constructor(cli: CodexCliClient, turnPreparer: ProviderTurnPreparer);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
+ cancelTurn(channelId: string): Promise<boolean>;
12
13
  dispose(): Promise<void>;
13
14
  }
@@ -25,6 +25,9 @@ export class CodexProviderAdapter {
25
25
  // session as it runs, so before this point there is nothing to report.
26
26
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.channelId) };
27
27
  }
28
+ async cancelTurn(channelId) {
29
+ return this.cli.cancelTurn(channelId);
30
+ }
28
31
  async dispose() {
29
32
  await this.cli.dispose();
30
33
  }
@@ -49,6 +49,7 @@ export declare class CodexCliClient {
49
49
  * worked a task without the agent being asked to report its own id.
50
50
  */
51
51
  sessionIdForChannel(channelId: string): string | undefined;
52
+ cancelTurn(channelId: string): Promise<boolean>;
52
53
  private readonly persistedSessions;
53
54
  private readonly closingSessions;
54
55
  private readonly pendingSessionStarts;
@@ -6,6 +6,7 @@ import spawn from 'cross-spawn';
6
6
  import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
7
7
  import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
8
8
  import { AcpProgressCollector } from '../acp-progress-collector.js';
9
+ import { ProviderTurnCancelledError } from '../provider-adapter.js';
9
10
  import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
10
11
  import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
11
12
  import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
@@ -34,6 +35,7 @@ const CODEX_CONTEXT_ROOT_DIRNAME = 'codex-context';
34
35
  const PROJECTION_UNAVAILABLE_PLACEHOLDER = '[codex-projection-unavailable]';
35
36
  const QUEUED_TURN_DROPPED_MESSAGE = 'Codex ACP session was reset after a failed turn; queued turns were dropped instead of replaying them on a fresh session';
36
37
  const require = createRequire(import.meta.url);
38
+ const CODEX_CANCELLED_REPLY_TEXT = 'Info: Operation cancelled by user';
37
39
  const DEFAULT_RUNTIME = {
38
40
  spawn,
39
41
  client,
@@ -230,6 +232,23 @@ export class CodexCliClient {
230
232
  sessionIdForChannel(channelId) {
231
233
  return this.channels.get(channelId)?.session?.sessionId;
232
234
  }
235
+ async cancelTurn(channelId) {
236
+ const state = this.channels.get(channelId);
237
+ const session = state?.session;
238
+ if (!state?.activeTurn || !session) {
239
+ return false;
240
+ }
241
+ this.clearIdleTimer(state);
242
+ this.idleBackendShutdown.cancel();
243
+ const activeSession = session;
244
+ if (typeof activeSession.cancel === 'function') {
245
+ await activeSession.cancel({ sessionId: session.sessionId });
246
+ return true;
247
+ }
248
+ const cancelMethod = this.runtime.methods.agent.session?.cancel ?? 'session/cancel';
249
+ await this.connection?.agent.notify(cancelMethod, { sessionId: session.sessionId });
250
+ return true;
251
+ }
233
252
  persistedSessions = new Map();
234
253
  closingSessions = new WeakSet();
235
254
  pendingSessionStarts = new Set();
@@ -831,10 +850,15 @@ export class CodexCliClient {
831
850
  catch (error) {
832
851
  throw markSessionTainted(error);
833
852
  }
853
+ const output = collector.getFinalText();
854
+ if (response.stopReason === 'cancelled') {
855
+ throw new ProviderTurnCancelledError('Codex ACP turn cancelled', {
856
+ publicReplyText: output || CODEX_CANCELLED_REPLY_TEXT,
857
+ });
858
+ }
834
859
  if (response.stopReason !== 'end_turn') {
835
860
  throw new Error(`Codex ACP turn stopped with stopReason "${response.stopReason}"`);
836
861
  }
837
- const output = collector.getFinalText();
838
862
  if (!output) {
839
863
  throw new Error('Codex ACP returned empty output');
840
864
  }
@@ -9,5 +9,6 @@ export declare class CopilotProviderAdapter implements ProviderAdapter {
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
10
  constructor(cli: CopilotCliClient, turnPreparer: ProviderTurnPreparer);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
+ cancelTurn(channelId: string): Promise<boolean>;
12
13
  dispose(): Promise<void>;
13
14
  }
@@ -25,6 +25,9 @@ export class CopilotProviderAdapter {
25
25
  // session as it runs, so before this point there is nothing to report.
26
26
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.channelId) };
27
27
  }
28
+ async cancelTurn(channelId) {
29
+ return this.cli.cancelTurn(channelId);
30
+ }
28
31
  async dispose() {
29
32
  await this.cli.dispose();
30
33
  }
@@ -51,6 +51,7 @@ export declare class CopilotCliClient {
51
51
  * worked a task without the agent being asked to report its own id.
52
52
  */
53
53
  sessionIdForChannel(channelId: string): string | undefined;
54
+ cancelTurn(channelId: string): Promise<boolean>;
54
55
  private readonly persistedSessions;
55
56
  private readonly closingSessions;
56
57
  private readonly pendingSessionStarts;
@@ -3,6 +3,7 @@ import spawn from 'cross-spawn';
3
3
  import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
4
4
  import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
5
5
  import { AcpProgressCollector } from '../acp-progress-collector.js';
6
+ import { ProviderTurnCancelledError } from '../provider-adapter.js';
6
7
  import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
7
8
  import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
8
9
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
@@ -170,6 +171,23 @@ export class CopilotCliClient {
170
171
  sessionIdForChannel(channelId) {
171
172
  return this.channels.get(channelId)?.session?.sessionId;
172
173
  }
174
+ async cancelTurn(channelId) {
175
+ const state = this.channels.get(channelId);
176
+ const session = state?.session;
177
+ if (!state?.activeTurn || !session) {
178
+ return false;
179
+ }
180
+ this.clearIdleTimer(state);
181
+ this.idleBackendShutdown.cancel();
182
+ const activeSession = session;
183
+ if (typeof activeSession.cancel === 'function') {
184
+ await activeSession.cancel({ sessionId: session.sessionId });
185
+ return true;
186
+ }
187
+ const cancelMethod = this.runtime.methods.agent.session?.cancel ?? 'session/cancel';
188
+ await this.connection?.agent.notify(cancelMethod, { sessionId: session.sessionId });
189
+ return true;
190
+ }
173
191
  persistedSessions = new Map();
174
192
  closingSessions = new WeakSet();
175
193
  pendingSessionStarts = new Set();
@@ -651,6 +669,9 @@ export class CopilotCliClient {
651
669
  catch (error) {
652
670
  throw markSessionTainted(error);
653
671
  }
672
+ if (response.stopReason === 'cancelled') {
673
+ throw new ProviderTurnCancelledError('Copilot ACP turn cancelled');
674
+ }
654
675
  if (response.stopReason !== 'end_turn') {
655
676
  throw new Error(`Copilot ACP turn stopped with stopReason "${response.stopReason}"`);
656
677
  }
@@ -30,6 +30,9 @@ class ProviderV2CompatibilityAdapter {
30
30
  turn: preparedTurn,
31
31
  }, options);
32
32
  }
33
+ async cancelTurn(channelId) {
34
+ return this.provider.cancelTurn?.(channelId) ?? false;
35
+ }
33
36
  async dispose() {
34
37
  await this.provider.shutdown?.();
35
38
  }
@@ -47,6 +50,9 @@ class CliBackedProviderV2 {
47
50
  // session as it runs, so before this point there is nothing to report.
48
51
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.turn.channelId) };
49
52
  }
53
+ async cancelTurn(channelId) {
54
+ return this.cli.cancelTurn(channelId);
55
+ }
50
56
  async shutdown() {
51
57
  await this.cli.dispose();
52
58
  }
@@ -33,8 +33,15 @@ export declare function createHostedProviderCapabilities(params: {
33
33
  sharedHostFallback?: SharedHostAttachmentMetadataFallbackCapability;
34
34
  }): ProviderCapabilities;
35
35
  export declare const TEXT_ONLY_HOSTED_PROVIDER_CAPABILITIES: ProviderCapabilities;
36
+ export declare class ProviderTurnCancelledError extends Error {
37
+ readonly publicReplyText?: string;
38
+ constructor(message?: string, options?: {
39
+ publicReplyText?: string;
40
+ });
41
+ }
36
42
  export interface ProviderAdapter {
37
43
  readonly capabilities: ProviderCapabilities;
38
44
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
45
+ cancelTurn?(channelId: string): Promise<boolean>;
39
46
  dispose?(): Promise<void>;
40
47
  }
@@ -42,3 +42,11 @@ export const TEXT_ONLY_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabi
42
42
  delivery: [],
43
43
  },
44
44
  });
45
+ export class ProviderTurnCancelledError extends Error {
46
+ publicReplyText;
47
+ constructor(message = 'provider turn cancelled', options) {
48
+ super(message);
49
+ this.name = 'ProviderTurnCancelledError';
50
+ this.publicReplyText = options?.publicReplyText;
51
+ }
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borgee/agents-host",
3
- "version": "0.2.62",
3
+ "version": "0.2.68",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -35,7 +35,7 @@
35
35
  "tsx": "^4.20.5",
36
36
  "typescript": "^5.9.3",
37
37
  "vitest": "^4.1.5",
38
- "@borgee/plugin-sdk": "0.12.0"
38
+ "@borgee/plugin-sdk": "0.12.1"
39
39
  },
40
40
  "scripts": {
41
41
  "predev": "pnpm --filter @borgee/plugin-sdk build",