@borgee/agents-host 0.2.74 → 0.2.84

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.
Files changed (46) hide show
  1. package/README.md +11 -24
  2. package/dist/agents-host.d.ts +6 -4
  3. package/dist/agents-host.js +117 -16
  4. package/dist/background-runs.d.ts +23 -0
  5. package/dist/background-runs.js +153 -0
  6. package/dist/chat/chat-control-plane.d.ts +6 -1
  7. package/dist/chat/sdk-chat-control-plane.d.ts +4 -2
  8. package/dist/chat/sdk-chat-control-plane.js +7 -0
  9. package/dist/context/claude-file-brief.js +3 -3
  10. package/dist/context/injection.d.ts +18 -12
  11. package/dist/context/injection.js +53 -53
  12. package/dist/context/prompt.js +10 -10
  13. package/dist/context/resolved-working-folder.d.ts +3 -0
  14. package/dist/context/resolved-working-folder.js +106 -0
  15. package/dist/context/turn-preparation.js +1 -1
  16. package/dist/managed-daemon.js +30 -30
  17. package/dist/plugin-sdk.js +179 -9
  18. package/dist/plugin-sdk.js.map +3 -3
  19. package/dist/policy/authorization-audit.d.ts +1 -1
  20. package/dist/policy/copilot-permission.d.ts +9 -0
  21. package/dist/policy/copilot-permission.js +120 -1
  22. package/dist/progress-to-activity.d.ts +1 -1
  23. package/dist/progress-to-activity.js +1 -0
  24. package/dist/providers/claude/adapter.d.ts +1 -1
  25. package/dist/providers/claude/adapter.js +7 -0
  26. package/dist/providers/claude/cli-client.js +4 -3
  27. package/dist/providers/codex/adapter.d.ts +1 -1
  28. package/dist/providers/codex/adapter.js +7 -0
  29. package/dist/providers/codex/cli-client.js +8 -7
  30. package/dist/providers/codex/project-doc.js +6 -6
  31. package/dist/providers/copilot/adapter.d.ts +6 -2
  32. package/dist/providers/copilot/adapter.js +27 -1
  33. package/dist/providers/copilot/cli-client.d.ts +37 -10
  34. package/dist/providers/copilot/cli-client.js +751 -122
  35. package/dist/providers/copilot/sdk-session.d.ts +149 -0
  36. package/dist/providers/copilot/sdk-session.js +981 -0
  37. package/dist/providers/create-provider.js +33 -0
  38. package/dist/providers/provider-adapter.d.ts +16 -1
  39. package/dist/providers/provider-adapter.js +13 -0
  40. package/dist/types.d.ts +32 -11
  41. package/package.json +3 -2
  42. package/skills/borgee-agent/references/task-properties.md +3 -3
  43. package/skills/borgee-agent/scripts/borgee-agent.mjs +1 -1
  44. package/skills/borgee-agent/scripts/borgee-agent.py +1 -1
  45. package/dist/context/resolved-workspace.d.ts +0 -3
  46. package/dist/context/resolved-workspace.js +0 -106
@@ -1,6 +1,6 @@
1
1
  import type { DebugLogger } from '../debug.js';
2
2
  import type { InternalPolicyMode } from '../compatibility-gates.js';
3
- export type AuthorizationAuditSurface = 'copilot-acp-permission' | 'localhost-gateway';
3
+ export type AuthorizationAuditSurface = 'copilot-acp-permission' | 'copilot-sdk-permission' | 'localhost-gateway';
4
4
  export interface AuthorizationAuditRecord {
5
5
  timestamp: string;
6
6
  surface: AuthorizationAuditSurface;
@@ -1,7 +1,9 @@
1
1
  import type { RequestPermissionRequest, RequestPermissionResponse } from '@agentclientprotocol/sdk';
2
+ import type { PermissionRequest as CopilotSdkPermissionRequest, PermissionRequestResult as CopilotSdkPermissionRequestResult } from '@github/copilot-sdk';
2
3
  import type { DebugLogger } from '../debug.js';
3
4
  import type { InternalPolicyMode } from '../compatibility-gates.js';
4
5
  import type { AuthorizationAuditSinkLike } from './authorization-audit.js';
6
+ import type { AuthorizationAuditSurface } from './authorization-audit.js';
5
7
  export interface CopilotPermissionPolicyContext {
6
8
  gateEnabled: boolean;
7
9
  policyMode: InternalPolicyMode;
@@ -10,7 +12,14 @@ export interface CopilotPermissionPolicyContext {
10
12
  auditSink?: AuthorizationAuditSinkLike;
11
13
  agentId?: string;
12
14
  channelId?: string;
15
+ sessionBound?: boolean;
13
16
  toolPermissionMode?: 'default' | 'deny-all';
17
+ auditSurface?: AuthorizationAuditSurface;
18
+ }
19
+ export interface CopilotSdkPermissionPolicyContext extends Omit<CopilotPermissionPolicyContext, 'params'> {
20
+ request: CopilotSdkPermissionRequest;
21
+ sessionId: string;
14
22
  }
15
23
  export declare function resolveCopilotPermissionResponse(context: CopilotPermissionPolicyContext): RequestPermissionResponse;
24
+ export declare function resolveCopilotSdkPermissionResponse(context: CopilotSdkPermissionPolicyContext): CopilotSdkPermissionRequestResult;
16
25
  export declare function createLegacyMissingAllowOptionError(): Error;
@@ -1,5 +1,8 @@
1
1
  import { summarizeError } from '../debug.js';
2
2
  const LEGACY_MISSING_ALLOW_OPTION_MESSAGE = 'Copilot ACP requested permission but did not offer an allow option';
3
+ const SDK_APPROVE_FOR_SESSION_OPTION_ID = 'sdk-approve-for-session';
4
+ const SDK_APPROVE_ONCE_OPTION_ID = 'sdk-approve-once';
5
+ const SDK_REJECT_OPTION_ID = 'sdk-reject';
3
6
  function legacySelectPermissionOption(options) {
4
7
  const option = options.find((candidate) => candidate.kind === 'allow_always')
5
8
  ?? options.find((candidate) => candidate.kind === 'allow_once');
@@ -71,7 +74,7 @@ function decisionToResponse(decision) {
71
74
  function appendAuditRecord(context, effectiveDecision, reason, policyDecision) {
72
75
  context.auditSink?.append({
73
76
  timestamp: new Date().toISOString(),
74
- surface: 'copilot-acp-permission',
77
+ surface: context.auditSurface ?? 'copilot-acp-permission',
75
78
  policyMode: context.policyMode,
76
79
  effectiveOutcome: effectiveDecision?.outcome ?? 'error',
77
80
  reason,
@@ -84,6 +87,30 @@ function appendAuditRecord(context, effectiveDecision, reason, policyDecision) {
84
87
  });
85
88
  }
86
89
  export function resolveCopilotPermissionResponse(context) {
90
+ if (context.sessionBound === false) {
91
+ const rejectOption = resolveRejectOption(context.params.options);
92
+ const unboundDecision = rejectOption
93
+ ? {
94
+ outcome: 'selected',
95
+ optionId: rejectOption.optionId,
96
+ optionKind: rejectOption.kind,
97
+ reason: 'unbound-session',
98
+ toolKind: normalizeToolKind(context.params.toolCall.kind),
99
+ }
100
+ : {
101
+ outcome: 'cancelled',
102
+ reason: 'unbound-session',
103
+ toolKind: normalizeToolKind(context.params.toolCall.kind),
104
+ };
105
+ context.logger.error('Copilot ACP permission request rejected for an unbound session', {
106
+ agentId: context.agentId,
107
+ sessionId: context.params.sessionId,
108
+ toolKind: unboundDecision.toolKind,
109
+ reason: unboundDecision.reason,
110
+ });
111
+ appendAuditRecord(context, unboundDecision, unboundDecision.reason, unboundDecision);
112
+ return decisionToResponse(unboundDecision);
113
+ }
87
114
  if (context.toolPermissionMode === 'deny-all') {
88
115
  const rejectOption = resolveRejectOption(context.params.options);
89
116
  const deniedDecision = rejectOption
@@ -206,6 +233,98 @@ export function resolveCopilotPermissionResponse(context) {
206
233
  return decisionToResponse(failedClosedDecision);
207
234
  }
208
235
  }
236
+ function sdkPermissionToolKind(request) {
237
+ switch (request.kind) {
238
+ case 'read':
239
+ return 'read';
240
+ case 'write':
241
+ return 'edit';
242
+ case 'shell':
243
+ return 'execute';
244
+ case 'url':
245
+ return 'fetch';
246
+ default:
247
+ return 'other';
248
+ }
249
+ }
250
+ function sdkPermissionTitle(request) {
251
+ if ('fullCommandText' in request && typeof request.fullCommandText === 'string') {
252
+ return request.fullCommandText;
253
+ }
254
+ if ('fileName' in request && typeof request.fileName === 'string') {
255
+ return request.fileName;
256
+ }
257
+ if ('toolName' in request && typeof request.toolName === 'string') {
258
+ return request.toolName;
259
+ }
260
+ return request.kind;
261
+ }
262
+ export function resolveCopilotSdkPermissionResponse(context) {
263
+ if (context.request.managedApprovalRequired === true) {
264
+ context.auditSink?.append({
265
+ timestamp: new Date().toISOString(),
266
+ surface: context.auditSurface ?? 'copilot-sdk-permission',
267
+ policyMode: context.policyMode,
268
+ effectiveOutcome: 'rejected',
269
+ reason: 'managed-approval-required',
270
+ agentId: context.agentId,
271
+ channelId: context.channelId,
272
+ selectedOptionKind: 'reject_once',
273
+ policyOutcome: 'selected',
274
+ policySelectedOptionKind: 'reject_once',
275
+ toolKind: sdkPermissionToolKind(context.request),
276
+ });
277
+ return {
278
+ kind: 'reject',
279
+ feedback: 'Managed policy requires interactive approval, which this hosted runtime cannot provide.',
280
+ };
281
+ }
282
+ const options = [
283
+ {
284
+ optionId: SDK_APPROVE_FOR_SESSION_OPTION_ID,
285
+ name: 'Allow for this session',
286
+ kind: 'allow_always',
287
+ },
288
+ {
289
+ optionId: SDK_APPROVE_ONCE_OPTION_ID,
290
+ name: 'Allow once',
291
+ kind: 'allow_once',
292
+ },
293
+ {
294
+ optionId: SDK_REJECT_OPTION_ID,
295
+ name: 'Reject',
296
+ kind: 'reject_once',
297
+ },
298
+ ];
299
+ const response = resolveCopilotPermissionResponse({
300
+ ...context,
301
+ params: {
302
+ sessionId: context.sessionId,
303
+ toolCall: {
304
+ toolCallId: requestToolCallId(context.request),
305
+ title: sdkPermissionTitle(context.request),
306
+ kind: sdkPermissionToolKind(context.request),
307
+ },
308
+ options,
309
+ },
310
+ });
311
+ if (response.outcome.outcome !== 'selected') {
312
+ return { kind: 'reject' };
313
+ }
314
+ switch (response.outcome.optionId) {
315
+ case SDK_APPROVE_FOR_SESSION_OPTION_ID:
316
+ return { kind: 'approve-for-session' };
317
+ case SDK_APPROVE_ONCE_OPTION_ID:
318
+ return { kind: 'approve-once' };
319
+ default:
320
+ return { kind: 'reject' };
321
+ }
322
+ }
323
+ function requestToolCallId(request) {
324
+ return 'toolCallId' in request && typeof request.toolCallId === 'string'
325
+ ? request.toolCallId
326
+ : 'copilot-sdk-permission';
327
+ }
209
328
  export function createLegacyMissingAllowOptionError() {
210
329
  return new Error(LEGACY_MISSING_ALLOW_OPTION_MESSAGE);
211
330
  }
@@ -2,7 +2,7 @@ import type { AgentActivity } from './plugin-sdk.js';
2
2
  import type { ProviderProgressUpdate } from './types.js';
3
3
  /** Everything a provider reports. The turn boundary is the reporter's own and never arrives here. */
4
4
  export type ReportedActivity = Exclude<AgentActivity, {
5
- shape: 'turn';
5
+ shape: 'turn' | 'background_run';
6
6
  }>;
7
7
  /**
8
8
  * Restates one progress update in the rail's own vocabulary.
@@ -19,6 +19,7 @@ export function toReportedActivity(update) {
19
19
  case 'activity':
20
20
  return { shape: 'activity', ...update.activity };
21
21
  case 'compaction':
22
+ case 'background_run':
22
23
  return null;
23
24
  }
24
25
  }
@@ -7,7 +7,7 @@ export declare class ClaudeProviderAdapter implements ProviderAdapter {
7
7
  private readonly cli;
8
8
  private readonly turnPreparer;
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
- constructor(cli: ClaudeCliClient, turnPreparer: ProviderTurnPreparer);
10
+ constructor(cli: ClaudeCliClient, turnPreparer: Pick<ProviderTurnPreparer, 'prepare'>);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
12
  cancelTurn(channelId: string): Promise<boolean>;
13
13
  dispose(): Promise<void>;
@@ -7,6 +7,13 @@ export const CLAUDE_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabilit
7
7
  reason: 'real-media-delivered',
8
8
  delivery: ['blob'],
9
9
  },
10
+ progressUpdates: {
11
+ support: 'supported',
12
+ },
13
+ backgroundRuns: {
14
+ lifecycle: 'not-supported',
15
+ targetedCancellation: 'not-supported',
16
+ },
10
17
  });
11
18
  export class ClaudeProviderAdapter {
12
19
  cli;
@@ -13,7 +13,7 @@ import { ProviderTurnCancelledError } from '../provider-adapter.js';
13
13
  import { readClaudeActivityMetadata } from './activity-metadata.js';
14
14
  import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
15
15
  import { buildClaudeSessionPromptAppend } from '../../context/prompt.js';
16
- import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
16
+ import { isDiscussionOnlyResolvedWorkingFolder } from '../../context/resolved-working-folder.js';
17
17
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
18
18
  const SESSION_TAINTED_ERRORS = new WeakSet();
19
19
  const IDLE_BACKEND_STOPPED_MESSAGE = 'Claude ACP backend stopped after idle timeout';
@@ -761,7 +761,7 @@ export class ClaudeCliClient {
761
761
  state.activeTurn = turn;
762
762
  try {
763
763
  state.cwd = this.resolveSessionCwd(turn.preparedTurn.promptContext);
764
- state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
764
+ state.toolPermissionMode = isDiscussionOnlyResolvedWorkingFolder(turn.preparedTurn.promptContext)
765
765
  ? 'deny-all'
766
766
  : 'default';
767
767
  const sessionPromptAppend = resolveTurnSessionPromptAppend(turn.preparedTurn);
@@ -939,7 +939,7 @@ export class ClaudeCliClient {
939
939
  }
940
940
  }
941
941
  resolveSessionCwd(promptContext) {
942
- return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
942
+ return promptContext?.resolvedWorkingFolder?.rootPath ?? this.runtime.cwd;
943
943
  }
944
944
  async recycleSessionIfScopeChanged(channelId, state) {
945
945
  const session = state.session;
@@ -1055,6 +1055,7 @@ export class ClaudeCliClient {
1055
1055
  logger: this.logger,
1056
1056
  agentId: this.resolveSessionStoreAgentId()?.trim() || undefined,
1057
1057
  channelId: state?.channelId,
1058
+ sessionBound: state !== undefined,
1058
1059
  toolPermissionMode: state?.toolPermissionMode ?? 'default',
1059
1060
  });
1060
1061
  }
@@ -7,7 +7,7 @@ export declare class CodexProviderAdapter implements ProviderAdapter {
7
7
  private readonly cli;
8
8
  private readonly turnPreparer;
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
- constructor(cli: CodexCliClient, turnPreparer: ProviderTurnPreparer);
10
+ constructor(cli: CodexCliClient, turnPreparer: Pick<ProviderTurnPreparer, 'prepare'>);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
12
  cancelTurn(channelId: string): Promise<boolean>;
13
13
  dispose(): Promise<void>;
@@ -7,6 +7,13 @@ export const CODEX_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabiliti
7
7
  reason: 'real-media-delivered',
8
8
  delivery: ['blob'],
9
9
  },
10
+ progressUpdates: {
11
+ support: 'supported',
12
+ },
13
+ backgroundRuns: {
14
+ lifecycle: 'not-supported',
15
+ targetedCancellation: 'not-supported',
16
+ },
10
17
  });
11
18
  export class CodexProviderAdapter {
12
19
  cli;
@@ -9,7 +9,7 @@ import { AcpProgressCollector } from '../acp-progress-collector.js';
9
9
  import { ProviderTurnCancelledError } from '../provider-adapter.js';
10
10
  import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
11
11
  import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
12
- import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
12
+ import { isDiscussionOnlyResolvedWorkingFolder } from '../../context/resolved-working-folder.js';
13
13
  import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
14
14
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
15
15
  const SESSION_TAINTED_ERRORS = new WeakSet();
@@ -488,7 +488,7 @@ export class CodexCliClient {
488
488
  state.activeTurn = turn;
489
489
  try {
490
490
  state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
491
- state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
491
+ state.toolPermissionMode = isDiscussionOnlyResolvedWorkingFolder(turn.preparedTurn.promptContext)
492
492
  ? 'deny-all'
493
493
  : 'default';
494
494
  const projectedPromptContext = await this.refreshProjectedPromptContextBestEffort(turn.channelId, turn.preparedTurn.promptContext);
@@ -658,7 +658,7 @@ export class CodexCliClient {
658
658
  }
659
659
  }
660
660
  async resolveSessionCwd(promptContext) {
661
- return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
661
+ return promptContext?.resolvedWorkingFolder?.rootPath ?? this.runtime.cwd;
662
662
  }
663
663
  async recycleSessionIfInjectionScopeChanged(channelId, state) {
664
664
  const session = state.session;
@@ -969,7 +969,9 @@ export class CodexCliClient {
969
969
  policyMode: 'enforce',
970
970
  params,
971
971
  logger: this.logger,
972
+ agentId: this.resolveSessionStoreAgentId()?.trim() || undefined,
972
973
  channelId: state?.channelId,
974
+ sessionBound: state !== undefined,
973
975
  toolPermissionMode: state?.toolPermissionMode ?? 'default',
974
976
  });
975
977
  }
@@ -1136,6 +1138,7 @@ export class CodexCliClient {
1136
1138
  this.closeSession(session);
1137
1139
  }
1138
1140
  this.channels.delete(channelId);
1141
+ this.idleBackendShutdown.reconcile();
1139
1142
  }
1140
1143
  async shutdownBackend(error) {
1141
1144
  if (this.backendClosed) {
@@ -1194,10 +1197,8 @@ export class CodexCliClient {
1194
1197
  if (this.pendingSessionStarts.size > 0) {
1195
1198
  return false;
1196
1199
  }
1197
- for (const state of this.channels.values()) {
1198
- if (state.activeTurn || state.queue.length > 0 || state.sessionPromise || state.processing) {
1199
- return false;
1200
- }
1200
+ if (this.channels.size > 0) {
1201
+ return false;
1201
1202
  }
1202
1203
  return true;
1203
1204
  }
@@ -2,12 +2,12 @@ import { buildAttentionSummaryLines } from '../../context/attention.js';
2
2
  import { buildCollaborationCapabilityDeclarationSummaryLines, buildMissedCollaborationDiagnosticSummaryLines, } from '../../context/collaboration-capabilities-diagnostics.js';
3
3
  import { buildCollaborationOutcomeSummaryLines } from '../../context/collaboration-outcome.js';
4
4
  import { MAIN_SESSION_DELEGATION_LINES } from '../../context/main-session-delegation.js';
5
- import { buildResolvedWorkspaceGuidanceLines } from '../../context/resolved-workspace.js';
5
+ import { buildResolvedWorkingFolderGuidanceLines } from '../../context/resolved-working-folder.js';
6
6
  import { buildSkillManualLines, buildSkillManualReadLine } from '../../context/skill-manual.js';
7
7
  import { buildTaskThreadCollaborationSummaryLines } from '../../context/task-thread-collaboration.js';
8
8
  const PROJECT_DOC_MAX_BYTES = 32 * 1024;
9
9
  // Every command the CLI carries needs the gateway credential file, so the capability is only
10
- // worth naming in a workspace that also names one; otherwise the model is handed an invocation
10
+ // worth naming in a project document that also names one; otherwise the model is handed an invocation
11
11
  // template it cannot fill.
12
12
  function buildSkillRuntimeLines(context) {
13
13
  if (!context?.skillRuntime) {
@@ -29,15 +29,15 @@ function buildGatewayCredentialLines(context) {
29
29
  export function buildCodexProjectDocument(context) {
30
30
  const sessionDelegationLines = context?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
31
31
  const lines = [
32
- '# Borgee channel workspace',
32
+ '# Borgee channel session',
33
33
  '',
34
- 'This workspace belongs to one Borgee channel session hosted by agents-host.',
34
+ 'This directory belongs to one Borgee channel session hosted by agents-host.',
35
35
  'Keep visible replies concise, do not claim actions you did not perform, and prefer the per-turn prompt when it provides newer turn-local details.',
36
36
  '',
37
37
  ...sessionDelegationLines,
38
38
  ...(() => {
39
- const workspaceLines = buildResolvedWorkspaceGuidanceLines(context);
40
- return workspaceLines.length > 0 ? ['', ...workspaceLines] : [];
39
+ const workingFolderLines = buildResolvedWorkingFolderGuidanceLines(context);
40
+ return workingFolderLines.length > 0 ? ['', ...workingFolderLines] : [];
41
41
  })(),
42
42
  ...(() => {
43
43
  const collaborationOutcomeLines = buildCollaborationOutcomeSummaryLines(context?.collaborationOutcome);
@@ -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 { CopilotCliClient } from './cli-client.js';
5
5
  export declare const COPILOT_HOSTED_PROVIDER_CAPABILITIES: import("../provider-adapter.js").ProviderCapabilities;
@@ -7,8 +7,12 @@ export declare class CopilotProviderAdapter implements ProviderAdapter {
7
7
  private readonly cli;
8
8
  private readonly turnPreparer;
9
9
  readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
10
- constructor(cli: CopilotCliClient, turnPreparer: ProviderTurnPreparer);
10
+ constructor(cli: CopilotCliClient, turnPreparer: Pick<ProviderTurnPreparer, 'prepare'>);
11
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
12
+ onAutonomousReply(handler: ((reply: ProviderAutonomousReply) => void) | undefined): void;
12
13
  cancelTurn(channelId: string): Promise<boolean>;
14
+ interruptTurn(channelId: string): Promise<boolean>;
15
+ cancelSession(channelId: string): Promise<boolean>;
16
+ cancelBackgroundRun(channelId: string, providerRunId: string): Promise<boolean>;
13
17
  dispose(): Promise<void>;
14
18
  }
@@ -7,6 +7,13 @@ export const COPILOT_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabili
7
7
  reason: 'real-media-delivered',
8
8
  delivery: ['blob'],
9
9
  },
10
+ progressUpdates: {
11
+ support: 'supported',
12
+ },
13
+ backgroundRuns: {
14
+ lifecycle: 'supported',
15
+ targetedCancellation: 'supported',
16
+ },
10
17
  });
11
18
  export class CopilotProviderAdapter {
12
19
  cli;
@@ -25,8 +32,27 @@ export class CopilotProviderAdapter {
25
32
  // session as it runs, so before this point there is nothing to report.
26
33
  return { ...parseProviderReply(text), sessionId: this.cli.sessionIdForChannel(input.channelId) };
27
34
  }
35
+ onAutonomousReply(handler) {
36
+ this.cli.onAutonomousReply(handler
37
+ ? (reply) => {
38
+ const parsed = parseProviderReply(reply.text);
39
+ if (parsed.text.trim()) {
40
+ handler({ ...reply, text: parsed.text });
41
+ }
42
+ }
43
+ : undefined);
44
+ }
28
45
  async cancelTurn(channelId) {
29
- return this.cli.cancelTurn(channelId);
46
+ return this.cli.cancelSession(channelId);
47
+ }
48
+ async interruptTurn(channelId) {
49
+ return this.cli.interruptTurn(channelId);
50
+ }
51
+ async cancelSession(channelId) {
52
+ return this.cli.cancelSession(channelId);
53
+ }
54
+ async cancelBackgroundRun(channelId, providerRunId) {
55
+ return this.cli.cancelBackgroundRun(channelId, providerRunId);
30
56
  }
31
57
  async dispose() {
32
58
  await this.cli.dispose();
@@ -3,9 +3,12 @@ import { PROTOCOL_VERSION, client, methods, ndJsonStream } from '@agentclientpro
3
3
  import { type DebugLogger } from '../../debug.js';
4
4
  import type { InternalPolicyMode } from '../../compatibility-gates.js';
5
5
  import type { AuthorizationAuditSinkLike } from '../../policy/authorization-audit.js';
6
- import type { PreparedProviderTurnInput, ProviderGenerateOptions } from '../../types.js';
6
+ import type { PreparedProviderTurnInput, ProviderAutonomousReply, ProviderGenerateOptions } from '../../types.js';
7
7
  import type { CopilotChannelSessionStore } from './session-store.js';
8
- interface CopilotAcpRuntime {
8
+ import { type CopilotSdkClientFactory } from './sdk-session.js';
9
+ interface CopilotRuntime {
10
+ transport: 'sdk' | 'acp';
11
+ createSdkClient: CopilotSdkClientFactory;
9
12
  spawn: typeof spawn;
10
13
  client: typeof client;
11
14
  ndJsonStream: typeof ndJsonStream;
@@ -15,6 +18,7 @@ interface CopilotAcpRuntime {
15
18
  idleSessionTtlMs: number;
16
19
  idleBackendShutdownMs: number;
17
20
  idleShutdownGracePeriodMs: number;
21
+ sessionCloseTimeoutMs: number;
18
22
  shutdownGracePeriodMs: number;
19
23
  shutdownForceKillWaitMs: number;
20
24
  fetch(input: string | URL, init?: RequestInit): Promise<Response>;
@@ -29,12 +33,12 @@ interface CopilotHostedImageInputConfig {
29
33
  agentApiKey?: string;
30
34
  }
31
35
  /**
32
- * Persistent ACP-backed client for the GitHub Copilot CLI (`copilot --acp`).
36
+ * Persistent session client for the GitHub Copilot CLI.
33
37
  *
34
- * The legacy one-shot `COPILOT_ARGS` / constructor args are intentionally
35
- * ignored here: the prototype always launches the child as `copilot --acp`,
36
- * performs the ACP initialize handshake once, then reuses one ACP session per
37
- * Borgee channel for the process lifetime.
38
+ * Production uses the Copilot SDK headless JSON-RPC transport. The ACP runtime
39
+ * remains injectable for compatibility tests. Both paths reuse one provider
40
+ * session per Borgee channel because the background-agent registry belongs to
41
+ * that session and later read_agent/write_agent turns must keep that identity.
38
42
  */
39
43
  export declare class CopilotCliClient {
40
44
  private readonly command;
@@ -45,15 +49,21 @@ export declare class CopilotCliClient {
45
49
  private readonly imageInputConfig;
46
50
  private readonly runtime;
47
51
  private readonly channels;
52
+ private readonly channelWorkerRoutes;
53
+ private readonly sessionAuthorizationLeases;
48
54
  /**
49
55
  * The provider session currently bound to channelId, or undefined before a
50
56
  * turn has opened one. Surfaced so agents-host can record which session
51
57
  * worked a task without the agent being asked to report its own id.
52
58
  */
53
59
  sessionIdForChannel(channelId: string): string | undefined;
60
+ interruptTurn(channelId: string): Promise<boolean>;
61
+ cancelSession(channelId: string): Promise<boolean>;
54
62
  cancelTurn(channelId: string): Promise<boolean>;
63
+ cancelBackgroundRun(channelId: string, providerRunId: string): Promise<boolean>;
55
64
  private readonly persistedSessions;
56
65
  private readonly closingSessions;
66
+ private readonly orphanedSessions;
57
67
  private readonly pendingSessionStarts;
58
68
  private readonly pendingSessionCloses;
59
69
  private readonly pendingSessionStoreOperations;
@@ -61,6 +71,7 @@ export declare class CopilotCliClient {
61
71
  private rejectFatalPromise;
62
72
  private child?;
63
73
  private connection?;
74
+ private sdkClient?;
64
75
  private startPromise?;
65
76
  private shutdownPromise?;
66
77
  private childExitPromise?;
@@ -71,6 +82,7 @@ export declare class CopilotCliClient {
71
82
  */
72
83
  private backendGeneration;
73
84
  private idleShutdownPromise?;
85
+ private backendShutdownInProgress;
74
86
  private fatalError;
75
87
  private disposing;
76
88
  private backendClosed;
@@ -78,19 +90,25 @@ export declare class CopilotCliClient {
78
90
  private sessionStoreLoadPromise;
79
91
  private sessionStoreWriteQueue;
80
92
  private sessionCapabilities;
93
+ private backendRecycleRequired;
94
+ private autonomousReplyHandler?;
81
95
  private readonly idleBackendShutdown;
82
- constructor(command: string, _ignoredArgs?: string[], runtimeOverrides?: Partial<CopilotAcpRuntime>, sessionStore?: CopilotChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, permissionPolicy?: CopilotPermissionPolicyOptions, imageInputConfig?: CopilotHostedImageInputConfig);
96
+ constructor(command: string, _ignoredArgs?: string[], runtimeOverrides?: Partial<CopilotRuntime>, sessionStore?: CopilotChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, permissionPolicy?: CopilotPermissionPolicyOptions, imageInputConfig?: CopilotHostedImageInputConfig);
83
97
  generateReply(turn: PreparedProviderTurnInput, options?: ProviderGenerateOptions): Promise<string>;
84
98
  generateReply(channelId: string, prompt: string, options?: ProviderGenerateOptions): Promise<string>;
99
+ onAutonomousReply(handler: ((reply: ProviderAutonomousReply) => void) | undefined): void;
85
100
  dispose(): Promise<void>;
86
101
  private ensureStarted;
87
102
  private startBackend;
103
+ private startSdkBackend;
88
104
  private getOrCreateChannelState;
105
+ private resolveChannelState;
89
106
  private processChannelQueue;
90
107
  private getOrCreateSession;
91
108
  private startFreshSession;
92
109
  private restoreOrCreateSession;
93
110
  private restoreSession;
111
+ private createSdkSessionAdapter;
94
112
  private clearBufferedSessionReplay;
95
113
  private resolveSessionCwd;
96
114
  private buildPromptInput;
@@ -99,15 +117,21 @@ export declare class CopilotCliClient {
99
117
  private runTurn;
100
118
  private raceWithFatal;
101
119
  private handlePermissionRequest;
120
+ private handleSdkPermissionRequest;
102
121
  private failAll;
103
122
  private invalidateSession;
104
123
  private closeSession;
124
+ private markSessionOrphaned;
105
125
  private rejectQueuedTurnsAfterSessionTaint;
106
126
  private clearIdleTimer;
107
- private findChannelStateBySessionId;
127
+ private bindSessionAuthorization;
128
+ private promoteWorkerRoute;
129
+ private releaseWorkerRoute;
130
+ private releaseInactiveWorkerRoute;
108
131
  private reconcileIdleChannelState;
109
132
  private ensureSessionStoreLoaded;
110
133
  private readPersistedSessionId;
134
+ private isPersistentRouteOwner;
111
135
  private persistSession;
112
136
  private persistSessionBestEffort;
113
137
  private clearPersistedSession;
@@ -119,14 +143,17 @@ export declare class CopilotCliClient {
119
143
  /**
120
144
  * Backend teardown without the fatal latch: the client stays usable and
121
145
  * `ensureStarted()` spawns a replacement process on the next turn. Only safe
122
- * while `isBackendIdle()` holds, because every channel's live ACP session is
146
+ * while `isBackendIdle()` holds, because every channel's live session is
123
147
  * dropped here and no turn is ever replayed onto its successor.
124
148
  */
125
149
  private shutdownIdleBackend;
126
150
  private isBackendIdle;
151
+ private reconcileBackendLifecycle;
152
+ private releaseBackendAuthorizationLeases;
127
153
  private closeBackendProcess;
128
154
  private waitForPendingSessionStarts;
129
155
  private waitForPendingSessionCloses;
156
+ private waitForSessionClose;
130
157
  private waitForChildExit;
131
158
  }
132
159
  export {};