@borgee/agents-host 0.2.44 → 0.2.62

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 (65) hide show
  1. package/README.md +24 -27
  2. package/dist/agents-host.d.ts +27 -5
  3. package/dist/agents-host.js +266 -201
  4. package/dist/chat/chat-control-plane.d.ts +3 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
  6. package/dist/chat/sdk-chat-control-plane.js +3 -0
  7. package/dist/cli.js +1 -5
  8. package/dist/compatibility-gates.d.ts +4 -0
  9. package/dist/compatibility-gates.js +20 -1
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +21 -0
  12. package/dist/context/claude-file-brief.d.ts +2 -0
  13. package/dist/context/claude-file-brief.js +83 -0
  14. package/dist/context/compaction.d.ts +20 -0
  15. package/dist/context/compaction.js +59 -0
  16. package/dist/context/injection.d.ts +58 -7
  17. package/dist/context/injection.js +370 -36
  18. package/dist/context/main-session-delegation.d.ts +1 -1
  19. package/dist/context/projection-strategy.d.ts +24 -0
  20. package/dist/context/projection-strategy.js +90 -0
  21. package/dist/context/prompt.d.ts +16 -1
  22. package/dist/context/prompt.js +464 -26
  23. package/dist/context/resolved-workspace.d.ts +3 -0
  24. package/dist/context/resolved-workspace.js +106 -0
  25. package/dist/context/skill-manual.d.ts +1 -0
  26. package/dist/context/skill-manual.js +4 -1
  27. package/dist/context/turn-preparation.d.ts +8 -2
  28. package/dist/context/turn-preparation.js +64 -14
  29. package/dist/gateway/localhost-gateway.js +4 -5
  30. package/dist/local-config.js +11 -1
  31. package/dist/managed-daemon.js +127 -9
  32. package/dist/plugin-sdk.js +264 -364
  33. package/dist/plugin-sdk.js.map +4 -4
  34. package/dist/policy/copilot-permission.d.ts +1 -0
  35. package/dist/policy/copilot-permission.js +18 -0
  36. package/dist/policy/gateway-authorization.js +2 -2
  37. package/dist/progress-to-activity.d.ts +16 -0
  38. package/dist/progress-to-activity.js +24 -0
  39. package/dist/projection-strategy-values.d.ts +4 -0
  40. package/dist/projection-strategy-values.js +28 -0
  41. package/dist/providers/acp-progress-collector.d.ts +44 -0
  42. package/dist/providers/acp-progress-collector.js +130 -0
  43. package/dist/providers/awaiting-user.d.ts +2 -3
  44. package/dist/providers/awaiting-user.js +5 -7
  45. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  46. package/dist/providers/claude/activity-metadata.js +81 -0
  47. package/dist/providers/claude/cli-client.d.ts +2 -3
  48. package/dist/providers/claude/cli-client.js +220 -120
  49. package/dist/providers/codex/cli-client.d.ts +2 -0
  50. package/dist/providers/codex/cli-client.js +28 -104
  51. package/dist/providers/codex/project-doc.js +12 -11
  52. package/dist/providers/copilot/cli-client.d.ts +1 -1
  53. package/dist/providers/copilot/cli-client.js +12 -86
  54. package/dist/providers/create-provider.d.ts +2 -0
  55. package/dist/providers/create-provider.js +22 -4
  56. package/dist/state-paths.d.ts +1 -1
  57. package/dist/state-paths.js +3 -3
  58. package/dist/task-thread-resolution.d.ts +3 -2
  59. package/dist/types.d.ts +143 -6
  60. package/package.json +2 -2
  61. package/skills/borgee-agent/SKILL.md +12 -4
  62. package/skills/borgee-agent/references/errors.md +2 -2
  63. package/skills/borgee-agent/references/task-properties.md +6 -2
  64. package/dist/durable-cursor-store.d.ts +0 -5
  65. package/dist/durable-cursor-store.js +0 -7
@@ -5,8 +5,11 @@ import { createRequire } from 'node:module';
5
5
  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
+ import { AcpProgressCollector } from '../acp-progress-collector.js';
8
9
  import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
9
10
  import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
11
+ import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
12
+ import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
10
13
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
11
14
  const SESSION_TAINTED_ERRORS = new WeakSet();
12
15
  const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
@@ -60,9 +63,6 @@ const DEFAULT_RUNTIME = {
60
63
  },
61
64
  fetch: async (input, init) => fetch(input, init),
62
65
  };
63
- function hasVisibleText(value) {
64
- return typeof value === 'string' && value.trim().length > 0;
65
- }
66
66
  function cloneDirectories(directories) {
67
67
  return directories ? [...directories] : undefined;
68
68
  }
@@ -103,84 +103,6 @@ function serializePersistedSessionRecord(record) {
103
103
  ...(record.cwd ? { cwd: record.cwd } : {}),
104
104
  });
105
105
  }
106
- function formatToolProgress(title, status) {
107
- const normalizedTitle = hasVisibleText(title) ? title.trim() : null;
108
- switch (status) {
109
- case 'completed':
110
- return normalizedTitle ? `Completed ${normalizedTitle}` : 'Completed tool call';
111
- case 'failed':
112
- return normalizedTitle ? `Failed ${normalizedTitle}` : 'Tool call failed';
113
- case 'pending':
114
- case 'in_progress':
115
- case undefined:
116
- case null:
117
- return normalizedTitle ? `Running ${normalizedTitle}…` : 'Running tool…';
118
- default:
119
- return normalizedTitle ? `${status} ${normalizedTitle}` : status;
120
- }
121
- }
122
- function formatPlanProgress(entries) {
123
- const current = entries.find((entry) => entry.status === 'in_progress') ??
124
- entries.find((entry) => entry.status === 'pending') ??
125
- entries[0];
126
- return hasVisibleText(current?.content) ? `Plan: ${current.content.trim()}` : null;
127
- }
128
- class CodexProgressCollector {
129
- onProgress;
130
- publicText = '';
131
- lastPublished = null;
132
- toolTitles = new Map();
133
- constructor(onProgress) {
134
- this.onProgress = onProgress;
135
- }
136
- consume(update) {
137
- switch (update.update.sessionUpdate) {
138
- case 'agent_message_chunk':
139
- if (update.update.content.type !== 'text') {
140
- return;
141
- }
142
- this.publicText += update.update.content.text;
143
- this.publish(this.publicText);
144
- return;
145
- case 'tool_call':
146
- this.toolTitles.set(update.update.toolCallId, update.update.title);
147
- this.publishFallback(formatToolProgress(update.update.title, update.update.status));
148
- return;
149
- case 'tool_call_update': {
150
- const nextTitle = update.update.title ?? this.toolTitles.get(update.update.toolCallId);
151
- if (hasVisibleText(nextTitle)) {
152
- this.toolTitles.set(update.update.toolCallId, nextTitle);
153
- }
154
- this.publishFallback(formatToolProgress(nextTitle, update.update.status));
155
- return;
156
- }
157
- case 'plan':
158
- this.publishFallback(formatPlanProgress(update.update.entries.map((entry) => ({
159
- content: entry.content,
160
- status: entry.status,
161
- }))));
162
- return;
163
- default:
164
- return;
165
- }
166
- }
167
- getFinalText() {
168
- return this.publicText.trim();
169
- }
170
- publishFallback(text) {
171
- if (hasVisibleText(this.publicText)) {
172
- return;
173
- }
174
- this.publish(text);
175
- }
176
- publish(text) {
177
- if (!this.onProgress || !hasVisibleText(text) || text === this.lastPublished) {
178
- return;
179
- }
180
- this.lastPublished = text;
181
- this.onProgress({ text });
182
- }
183
- }
184
106
  function createDeferredTurn(channelId, preparedTurn, sessionPersistence, options) {
185
107
  let settled = false;
186
108
  let resolvePromise;
@@ -273,26 +195,6 @@ function markSessionTainted(error) {
273
195
  function isSessionTainted(error) {
274
196
  return error instanceof Error && SESSION_TAINTED_ERRORS.has(error);
275
197
  }
276
- function createPermissionResponse(params) {
277
- const options = Array.isArray(params.options) ? params.options : [];
278
- const preferredKinds = ['allow_once', 'allow_always', 'reject_once', 'reject_always'];
279
- for (const kind of preferredKinds) {
280
- const match = options.find((option) => option.kind === kind);
281
- if (match) {
282
- return {
283
- outcome: {
284
- outcome: 'selected',
285
- optionId: match.optionId,
286
- },
287
- };
288
- }
289
- }
290
- return {
291
- outcome: {
292
- outcome: 'cancelled',
293
- },
294
- };
295
- }
296
198
  function resolveBundledCodexAdapterPath() {
297
199
  return require.resolve('@agentclientprotocol/codex-acp');
298
200
  }
@@ -503,7 +405,7 @@ export class CodexCliClient {
503
405
  const stream = this.runtime.ndJsonStream(output, input);
504
406
  const app = this.runtime
505
407
  .client({ name: 'borgee-agents-host' })
506
- .onRequest(this.runtime.methods.client.session.requestPermission, ({ params }) => (createPermissionResponse(params)));
408
+ .onRequest(this.runtime.methods.client.session.requestPermission, ({ params }) => (this.handlePermissionRequest(params)));
507
409
  const connection = app.connect(stream);
508
410
  this.connection = connection;
509
411
  void connection.closed.then(() => {
@@ -567,6 +469,9 @@ export class CodexCliClient {
567
469
  state.activeTurn = turn;
568
470
  try {
569
471
  state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
472
+ state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
473
+ ? 'deny-all'
474
+ : 'default';
570
475
  const projectedPromptContext = await this.refreshProjectedPromptContextBestEffort(turn.channelId, turn.preparedTurn.promptContext);
571
476
  state.additionalDirectories = this.resolveSessionAdditionalDirectories(projectedPromptContext);
572
477
  state.visibilityKey = this.resolveSessionVisibilityKey(projectedPromptContext);
@@ -734,7 +639,7 @@ export class CodexCliClient {
734
639
  }
735
640
  }
736
641
  async resolveSessionCwd(promptContext) {
737
- return promptContext?.taskWorkspace?.rootPath ?? this.runtime.cwd;
642
+ return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
738
643
  }
739
644
  async recycleSessionIfInjectionScopeChanged(channelId, state) {
740
645
  const session = state.session;
@@ -906,7 +811,7 @@ export class CodexCliClient {
906
811
  const promptFailure = new Promise((_, reject) => {
907
812
  void promptPromise.catch((error) => reject(markSessionTainted(error)));
908
813
  });
909
- const collector = new CodexProgressCollector(options?.onProgress);
814
+ const collector = new AcpProgressCollector(options?.onProgress);
910
815
  for (;;) {
911
816
  let update;
912
817
  try {
@@ -1033,6 +938,25 @@ export class CodexCliClient {
1033
938
  clearTimeout(state.idleTimer);
1034
939
  state.idleTimer = undefined;
1035
940
  }
941
+ handlePermissionRequest(params) {
942
+ const state = this.findChannelStateBySessionId(params.sessionId);
943
+ return resolveCopilotPermissionResponse({
944
+ gateEnabled: true,
945
+ policyMode: 'enforce',
946
+ params,
947
+ logger: this.logger,
948
+ channelId: state?.channelId,
949
+ toolPermissionMode: state?.toolPermissionMode ?? 'default',
950
+ });
951
+ }
952
+ findChannelStateBySessionId(sessionId) {
953
+ for (const state of this.channels.values()) {
954
+ if (state.session?.sessionId === sessionId) {
955
+ return state;
956
+ }
957
+ }
958
+ return undefined;
959
+ }
1036
960
  reconcileIdleChannelState(channelId, state) {
1037
961
  if (this.channels.get(channelId) !== state) {
1038
962
  return;
@@ -2,17 +2,20 @@ 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 { buildSkillManualLines } from '../../context/skill-manual.js';
5
+ import { buildResolvedWorkspaceGuidanceLines } from '../../context/resolved-workspace.js';
6
+ import { buildSkillManualLines, buildSkillManualReadLine } from '../../context/skill-manual.js';
6
7
  import { buildTaskThreadCollaborationSummaryLines } from '../../context/task-thread-collaboration.js';
7
8
  const PROJECT_DOC_MAX_BYTES = 32 * 1024;
8
9
  // Every command the CLI carries needs the gateway credential file, so the capability is only
9
10
  // worth naming in a workspace that also names one; otherwise the model is handed an invocation
10
11
  // template it cannot fill.
11
12
  function buildSkillRuntimeLines(context) {
12
- if (!context?.skillRuntime || !context.gatewayCredentialPath) {
13
+ if (!context?.skillRuntime) {
13
14
  return [];
14
15
  }
15
- return buildSkillManualLines(context.skillRuntime);
16
+ return context.gatewayCredentialPath
17
+ ? buildSkillManualLines(context.skillRuntime)
18
+ : [buildSkillManualReadLine(context.skillRuntime)];
16
19
  }
17
20
  // The credential file is the whole handoff: it carries the gateway's base URL along with the
18
21
  // channel id and the token, and the CLI reads all three out of it. Naming the base URL separately
@@ -24,20 +27,18 @@ function buildGatewayCredentialLines(context) {
24
27
  return [`Gateway credential file: ${context.gatewayCredentialPath}`];
25
28
  }
26
29
  export function buildCodexProjectDocument(context) {
30
+ const sessionDelegationLines = context?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
27
31
  const lines = [
28
32
  '# Borgee channel workspace',
29
33
  '',
30
34
  'This workspace belongs to one Borgee channel session hosted by agents-host.',
31
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.',
32
36
  '',
33
- ...MAIN_SESSION_DELEGATION_LINES,
34
- ...(context?.taskWorkspace
35
- ? [
36
- '',
37
- `Writable task workspace root: ${context.taskWorkspace.rootPath}`,
38
- 'That writable task workspace is local to agents-host for the current task thread and does not imply that the target repository has already been checked out there.',
39
- ]
40
- : []),
37
+ ...sessionDelegationLines,
38
+ ...(() => {
39
+ const workspaceLines = buildResolvedWorkspaceGuidanceLines(context);
40
+ return workspaceLines.length > 0 ? ['', ...workspaceLines] : [];
41
+ })(),
41
42
  ...(() => {
42
43
  const collaborationOutcomeLines = buildCollaborationOutcomeSummaryLines(context?.collaborationOutcome);
43
44
  return collaborationOutcomeLines.length > 0 ? ['', ...collaborationOutcomeLines] : [];
@@ -103,7 +103,7 @@ export declare class CopilotCliClient {
103
103
  private closeSession;
104
104
  private rejectQueuedTurnsAfterSessionTaint;
105
105
  private clearIdleTimer;
106
- private findChannelIdBySessionId;
106
+ private findChannelStateBySessionId;
107
107
  private reconcileIdleChannelState;
108
108
  private ensureSessionStoreLoaded;
109
109
  private readPersistedSessionId;
@@ -2,7 +2,9 @@ import { Readable, Writable } from 'node:stream';
2
2
  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
+ import { AcpProgressCollector } from '../acp-progress-collector.js';
5
6
  import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
7
+ import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
6
8
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
7
9
  const SESSION_TAINTED_ERRORS = new WeakSet();
8
10
  const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
@@ -36,9 +38,6 @@ const DEFAULT_SESSION_CAPABILITIES = {
36
38
  loadSession: false,
37
39
  resumeSession: false,
38
40
  };
39
- function hasVisibleText(value) {
40
- return typeof value === 'string' && value.trim().length > 0;
41
- }
42
41
  function isAbsoluteHttpUrl(value) {
43
42
  return value.startsWith('http://') || value.startsWith('https://');
44
43
  }
@@ -61,84 +60,6 @@ function isSameOrigin(left, right) {
61
60
  function asImagePart(part) {
62
61
  return part.type === 'image' ? part : null;
63
62
  }
64
- function formatToolProgress(title, status) {
65
- const normalizedTitle = hasVisibleText(title) ? title.trim() : null;
66
- switch (status) {
67
- case 'completed':
68
- return normalizedTitle ? `Completed ${normalizedTitle}` : 'Completed tool call';
69
- case 'failed':
70
- return normalizedTitle ? `Failed ${normalizedTitle}` : 'Tool call failed';
71
- case 'pending':
72
- case 'in_progress':
73
- case undefined:
74
- case null:
75
- return normalizedTitle ? `Running ${normalizedTitle}…` : 'Running tool…';
76
- default:
77
- return normalizedTitle ? `${status} ${normalizedTitle}` : status;
78
- }
79
- }
80
- function formatPlanProgress(entries) {
81
- const current = entries.find((entry) => entry.status === 'in_progress') ??
82
- entries.find((entry) => entry.status === 'pending') ??
83
- entries[0];
84
- return hasVisibleText(current?.content) ? `Plan: ${current.content.trim()}` : null;
85
- }
86
- class CopilotProgressCollector {
87
- onProgress;
88
- publicText = '';
89
- lastPublished = null;
90
- toolTitles = new Map();
91
- constructor(onProgress) {
92
- this.onProgress = onProgress;
93
- }
94
- consume(update) {
95
- switch (update.update.sessionUpdate) {
96
- case 'agent_message_chunk':
97
- if (update.update.content.type !== 'text') {
98
- return;
99
- }
100
- this.publicText += update.update.content.text;
101
- this.publish(this.publicText);
102
- return;
103
- case 'tool_call':
104
- this.toolTitles.set(update.update.toolCallId, update.update.title);
105
- this.publishFallback(formatToolProgress(update.update.title, update.update.status));
106
- return;
107
- case 'tool_call_update': {
108
- const nextTitle = update.update.title ?? this.toolTitles.get(update.update.toolCallId);
109
- if (hasVisibleText(nextTitle)) {
110
- this.toolTitles.set(update.update.toolCallId, nextTitle);
111
- }
112
- this.publishFallback(formatToolProgress(nextTitle, update.update.status));
113
- return;
114
- }
115
- case 'plan':
116
- this.publishFallback(formatPlanProgress(update.update.entries.map((entry) => ({
117
- content: entry.content,
118
- status: entry.status,
119
- }))));
120
- return;
121
- default:
122
- return;
123
- }
124
- }
125
- getFinalText() {
126
- return this.publicText.trim();
127
- }
128
- publishFallback(text) {
129
- if (hasVisibleText(this.publicText)) {
130
- return;
131
- }
132
- this.publish(text);
133
- }
134
- publish(text) {
135
- if (!this.onProgress || !hasVisibleText(text) || text === this.lastPublished) {
136
- return;
137
- }
138
- this.lastPublished = text;
139
- this.onProgress({ text });
140
- }
141
- }
142
63
  function createDeferredTurn(channelId, preparedTurn, sessionPersistence, options) {
143
64
  let settled = false;
144
65
  let resolvePromise;
@@ -487,6 +408,9 @@ export class CopilotCliClient {
487
408
  state.activeTurn = turn;
488
409
  try {
489
410
  state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
411
+ state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
412
+ ? 'deny-all'
413
+ : 'default';
490
414
  await this.ensureStarted();
491
415
  await this.recycleSessionIfCwdChanged(channelId, state);
492
416
  const session = await this.getOrCreateSession(channelId, state);
@@ -647,7 +571,7 @@ export class CopilotCliClient {
647
571
  }
648
572
  }
649
573
  async resolveSessionCwd(promptContext) {
650
- return promptContext?.taskWorkspace?.rootPath ?? this.runtime.cwd;
574
+ return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
651
575
  }
652
576
  async buildPromptInput(turn) {
653
577
  const imageParts = turn.incomingParts.map(asImagePart).filter((part) => part !== null);
@@ -707,7 +631,7 @@ export class CopilotCliClient {
707
631
  const promptFailure = new Promise((_, reject) => {
708
632
  void promptPromise.catch((error) => reject(markSessionTainted(error)));
709
633
  });
710
- const collector = new CopilotProgressCollector(options?.onProgress);
634
+ const collector = new AcpProgressCollector(options?.onProgress);
711
635
  for (;;) {
712
636
  let update;
713
637
  try {
@@ -746,6 +670,7 @@ export class CopilotCliClient {
746
670
  return Promise.race([promise, this.fatalPromise]);
747
671
  }
748
672
  handlePermissionRequest(params) {
673
+ const state = this.findChannelStateBySessionId(params.sessionId);
749
674
  return resolveCopilotPermissionResponse({
750
675
  gateEnabled: this.permissionPolicy.gateEnabled ?? false,
751
676
  policyMode: this.permissionPolicy.policyMode ?? 'audit-only',
@@ -753,7 +678,8 @@ export class CopilotCliClient {
753
678
  logger: this.logger,
754
679
  auditSink: this.permissionPolicy.auditSink,
755
680
  agentId: this.resolveSessionStoreAgentId()?.trim() || undefined,
756
- channelId: this.findChannelIdBySessionId(params.sessionId),
681
+ channelId: state?.channelId,
682
+ toolPermissionMode: state?.toolPermissionMode ?? 'default',
757
683
  });
758
684
  }
759
685
  failAll(error) {
@@ -841,10 +767,10 @@ export class CopilotCliClient {
841
767
  clearTimeout(state.idleTimer);
842
768
  state.idleTimer = undefined;
843
769
  }
844
- findChannelIdBySessionId(sessionId) {
770
+ findChannelStateBySessionId(sessionId) {
845
771
  for (const state of this.channels.values()) {
846
772
  if (state.session?.sessionId === sessionId) {
847
- return state.channelId;
773
+ return state;
848
774
  }
849
775
  }
850
776
  return undefined;
@@ -1,5 +1,6 @@
1
1
  import type { DebugLogger } from '../debug.js';
2
2
  import type { ProviderRuntimeConfig } from '../types.js';
3
+ import type { ChatControlPlane } from '../chat/chat-control-plane.js';
3
4
  import { type ProviderAdapter } from './provider-adapter.js';
4
5
  import type { LocalhostGatewayContextPublisher } from '../context/injection.js';
5
6
  import type { AuthorizationAuditSinkLike } from '../policy/authorization-audit.js';
@@ -7,6 +8,7 @@ interface CreateProviderOptions {
7
8
  compatibilityGates?: ReadonlySet<string>;
8
9
  localhostGateway?: LocalhostGatewayContextPublisher;
9
10
  authorizationAuditSink?: AuthorizationAuditSinkLike;
11
+ controlPlane?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks' | 'listUsers'>;
10
12
  }
11
13
  export declare function createProvider(config: ProviderRuntimeConfig, debugLogger?: DebugLogger, options?: CreateProviderOptions): ProviderAdapter;
12
14
  export {};
@@ -1,7 +1,8 @@
1
1
  import { FileChannelContextStore } from '../context/injection.js';
2
+ import { ServerProjectionStrategyResolver } from '../context/projection-strategy.js';
2
3
  import { createProviderConnectionsSessionStore } from '../connections-state-store.js';
3
4
  import { ProviderTurnPreparer } from '../context/turn-preparation.js';
4
- import { CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, CODEX_PROVIDER_COMPATIBILITY_GATE, COPILOT_PROVIDER_V2_COMPATIBILITY_GATE, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, parseInternalProviderImplementationOverrides, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, SKILL_RUNTIME_COMPATIBILITY_GATE, } from '../compatibility-gates.js';
5
+ import { CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, CODEX_PROVIDER_COMPATIBILITY_GATE, COPILOT_PROVIDER_V2_COMPATIBILITY_GATE, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, parseInternalProviderImplementationOverrides, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, resolveInternalProjectionStrategy, SKILL_RUNTIME_COMPATIBILITY_GATE, } from '../compatibility-gates.js';
5
6
  import { resolveIdleBackendShutdownMs } from './idle-backend-shutdown.js';
6
7
  import { ClaudeCliClient } from './claude/cli-client.js';
7
8
  import { ClaudeProviderAdapter } from './claude/adapter.js';
@@ -52,7 +53,6 @@ class CliBackedProviderV2 {
52
53
  }
53
54
  class CopilotProviderV2 extends CliBackedProviderV2 {
54
55
  }
55
- const TASK_WORKSPACE_ROOT_DIR_ENV = 'AGENTS_HOST_INTERNAL_TASK_WORKSPACE_ROOT_DIR';
56
56
  const PROVIDER_V2_COMPATIBILITY_GATES = {
57
57
  copilot: COPILOT_PROVIDER_V2_COMPATIBILITY_GATE,
58
58
  };
@@ -131,13 +131,31 @@ export function createProvider(config, debugLogger, options = {}) {
131
131
  const channelContextStore = contextInjectionGateEnabled
132
132
  ? new FileChannelContextStore(config.stateRootDir, {
133
133
  skillRuntimeEnabled: skillRuntimeGateEnabled,
134
- taskWorkspaceRootDir: process.env[TASK_WORKSPACE_ROOT_DIR_ENV],
135
134
  localhostGateway: skillRuntimeGateEnabled && localhostGatewayGateEnabled
136
135
  ? options.localhostGateway
137
136
  : undefined,
137
+ taskReader: options.controlPlane,
138
+ allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
139
+ resolveStableAgentId: config.resolveStableAgentId,
138
140
  })
139
141
  : undefined;
140
- const turnPreparer = new ProviderTurnPreparer(channelContextStore, debugLogger);
142
+ const resolveInternalProjection = () => resolveInternalProjectionStrategy();
143
+ const projectionStrategyResolver = new ServerProjectionStrategyResolver({
144
+ baseUrl: config.borgeeBaseUrl,
145
+ agentApiKey: config.agentApiKey,
146
+ resolveStableAgentId: config.resolveStableAgentId,
147
+ fallback: resolveInternalProjection,
148
+ logger: debugLogger,
149
+ taskReader: options.controlPlane,
150
+ });
151
+ const turnPreparer = new ProviderTurnPreparer(channelContextStore, debugLogger, {
152
+ resolveProjectionStrategy: async (input) => {
153
+ if (input.provider !== 'claude') {
154
+ return resolveInternalProjection();
155
+ }
156
+ return projectionStrategyResolver.resolve(input.channelId);
157
+ },
158
+ });
141
159
  switch (config.provider) {
142
160
  case 'claude': {
143
161
  return createClaudeProvider(config, debugLogger, connectionsStateGateEnabled, turnPreparer, idleBackendShutdownMs);
@@ -20,11 +20,11 @@ export declare function resolveManagedDaemonLogPath(rootPath: string): string;
20
20
  export declare function resolveManagedRuntimeSettingsPath(rootPath: string): string;
21
21
  export declare function resolveManagedBootstrapLockPath(rootPath: string): string;
22
22
  export declare function resolveLocalConfigAgentStateRoot(rootPath: string, agentKey: string): string;
23
- export declare function resolveAgentCursorPath(stateRootDir: string, agentId: string): string;
24
23
  export declare function resolveSharedProtocolKickoffDecisionPath(stateRootDir: string, channelId: string, anchorMessageId: string): string;
25
24
  export declare function resolveSharedProtocolStatusPath(stateRootDir: string, channelId: string, anchorMessageId: string): string;
26
25
  export declare function resolveConnectionsStatePath(stateRootDir: string): string;
27
26
  export declare function resolveAttentionStatePath(stateRootDir: string, channelId: string): string;
27
+ export declare function resolveCompactionStatePath(stateRootDir: string, channelId: string): string;
28
28
  export declare function resolveAuthorizationAuditPath(stateRootDir: string): string;
29
29
  export declare function resolvePreviousAuthorizationAuditPath(stateRootDir: string): string;
30
30
  export declare function resolveClaudeSessionMapPath(stateRootDir: string, agentId: string): string;
@@ -129,9 +129,6 @@ export function resolveManagedBootstrapLockPath(rootPath) {
129
129
  export function resolveLocalConfigAgentStateRoot(rootPath, agentKey) {
130
130
  return join(resolveManagedStateRoot(rootPath), `${sanitizeStateRootLabel(agentKey)}-${hashStateRootKey(agentKey)}`);
131
131
  }
132
- export function resolveAgentCursorPath(stateRootDir, agentId) {
133
- return join(stateRootDir, `bpp-cursor-${encodeSegment(agentId)}.json`);
134
- }
135
132
  export function resolveSharedProtocolKickoffDecisionPath(stateRootDir, channelId, anchorMessageId) {
136
133
  return join(dirname(resolve(stateRootDir)), 'protocol-kickoff-decisions', `${encodeSegment(channelId)}--${encodeSegment(anchorMessageId)}.json`);
137
134
  }
@@ -144,6 +141,9 @@ export function resolveConnectionsStatePath(stateRootDir) {
144
141
  export function resolveAttentionStatePath(stateRootDir, channelId) {
145
142
  return join(stateRootDir, 'attention-state', `${encodeSegment(channelId)}.json`);
146
143
  }
144
+ export function resolveCompactionStatePath(stateRootDir, channelId) {
145
+ return join(stateRootDir, 'compaction-state', `${encodeSegment(channelId)}.json`);
146
+ }
147
147
  export function resolveAuthorizationAuditPath(stateRootDir) {
148
148
  return join(stateRootDir, 'authorization-audit.jsonl');
149
149
  }
@@ -1,10 +1,11 @@
1
1
  import type { ChatControlPlane } from './chat/chat-control-plane.js';
2
2
  import type { Task } from './types.js';
3
+ type TaskThreadResolutionControlPlane = Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks'>;
3
4
  interface WaitForTaskForThreadOptions {
4
5
  preferredTaskId?: string;
5
6
  attempts?: number;
6
7
  delayMs?: number;
7
8
  }
8
- export declare function findTaskForThread(controlPlane: ChatControlPlane, threadId: string, preferredTaskId?: string): Promise<Task | null>;
9
- export declare function waitForTaskForThread(controlPlane: ChatControlPlane, threadId: string, options?: WaitForTaskForThreadOptions): Promise<Task | null>;
9
+ export declare function findTaskForThread(controlPlane: TaskThreadResolutionControlPlane, threadId: string, preferredTaskId?: string): Promise<Task | null>;
10
+ export declare function waitForTaskForThread(controlPlane: TaskThreadResolutionControlPlane, threadId: string, options?: WaitForTaskForThreadOptions): Promise<Task | null>;
10
11
  export {};