@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.
- package/README.md +24 -27
- package/dist/agents-host.d.ts +27 -5
- package/dist/agents-host.js +266 -201
- package/dist/chat/chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
- package/dist/chat/sdk-chat-control-plane.js +3 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +20 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +21 -0
- package/dist/context/claude-file-brief.d.ts +2 -0
- package/dist/context/claude-file-brief.js +83 -0
- package/dist/context/compaction.d.ts +20 -0
- package/dist/context/compaction.js +59 -0
- package/dist/context/injection.d.ts +58 -7
- package/dist/context/injection.js +370 -36
- package/dist/context/main-session-delegation.d.ts +1 -1
- package/dist/context/projection-strategy.d.ts +24 -0
- package/dist/context/projection-strategy.js +90 -0
- package/dist/context/prompt.d.ts +16 -1
- package/dist/context/prompt.js +464 -26
- package/dist/context/resolved-workspace.d.ts +3 -0
- package/dist/context/resolved-workspace.js +106 -0
- package/dist/context/skill-manual.d.ts +1 -0
- package/dist/context/skill-manual.js +4 -1
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +64 -14
- package/dist/gateway/localhost-gateway.js +4 -5
- package/dist/local-config.js +11 -1
- package/dist/managed-daemon.js +127 -9
- package/dist/plugin-sdk.js +264 -364
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/policy/copilot-permission.d.ts +1 -0
- package/dist/policy/copilot-permission.js +18 -0
- package/dist/policy/gateway-authorization.js +2 -2
- package/dist/progress-to-activity.d.ts +16 -0
- package/dist/progress-to-activity.js +24 -0
- package/dist/projection-strategy-values.d.ts +4 -0
- package/dist/projection-strategy-values.js +28 -0
- package/dist/providers/acp-progress-collector.d.ts +44 -0
- package/dist/providers/acp-progress-collector.js +130 -0
- package/dist/providers/awaiting-user.d.ts +2 -3
- package/dist/providers/awaiting-user.js +5 -7
- package/dist/providers/claude/activity-metadata.d.ts +14 -0
- package/dist/providers/claude/activity-metadata.js +81 -0
- package/dist/providers/claude/cli-client.d.ts +2 -3
- package/dist/providers/claude/cli-client.js +220 -120
- package/dist/providers/codex/cli-client.d.ts +2 -0
- package/dist/providers/codex/cli-client.js +28 -104
- package/dist/providers/codex/project-doc.js +12 -11
- package/dist/providers/copilot/cli-client.d.ts +1 -1
- package/dist/providers/copilot/cli-client.js +12 -86
- package/dist/providers/create-provider.d.ts +2 -0
- package/dist/providers/create-provider.js +22 -4
- package/dist/state-paths.d.ts +1 -1
- package/dist/state-paths.js +3 -3
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +143 -6
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +12 -4
- package/skills/borgee-agent/references/errors.md +2 -2
- package/skills/borgee-agent/references/task-properties.md +6 -2
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
function buildWorkspaceRootLine(context) {
|
|
2
|
+
const resolvedWorkspace = context.resolvedWorkspace;
|
|
3
|
+
if (!resolvedWorkspace) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
if (resolvedWorkspace.authority === 'task-execution-target') {
|
|
7
|
+
return `Explicit execution local directory for this task thread: ${resolvedWorkspace.rootPath}.`;
|
|
8
|
+
}
|
|
9
|
+
if (resolvedWorkspace.authority === 'task-thread-scratch') {
|
|
10
|
+
return `Discussion-only scratch workspace for this task thread: ${resolvedWorkspace.rootPath}.`;
|
|
11
|
+
}
|
|
12
|
+
if (context.taskAssignmentContext?.active === true) {
|
|
13
|
+
return `Writable workspace root inherited from this task thread's channel: ${resolvedWorkspace.rootPath}.`;
|
|
14
|
+
}
|
|
15
|
+
return `Writable workspace root for this channel: ${resolvedWorkspace.rootPath}.`;
|
|
16
|
+
}
|
|
17
|
+
function buildWorkspaceModeLine(context) {
|
|
18
|
+
if (context.taskAssignmentContext?.active !== true || !context.resolvedWorkspace) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (context.resolvedWorkspace.authority === 'task-execution-target') {
|
|
22
|
+
return 'This task thread has a valid explicit execution.local_directory target, so execution is bound to that exact existing local directory.';
|
|
23
|
+
}
|
|
24
|
+
if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
|
|
25
|
+
return context.resolvedWorkspace.reason === 'cross-agent-independent-workspace-disabled'
|
|
26
|
+
? 'This task thread has an explicit execution.local_directory target, but this agents-host currently disables cross-agent independent-workspace handoff, so it is discussion-only.'
|
|
27
|
+
: 'This task thread has no valid explicit execution.local_directory target, so it is discussion-only.';
|
|
28
|
+
}
|
|
29
|
+
return 'This task thread inherits the channel workspace rather than using a task-isolated workspace.';
|
|
30
|
+
}
|
|
31
|
+
function buildWorkspaceLocalityLine(context) {
|
|
32
|
+
if (!context.resolvedWorkspace) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (context.resolvedWorkspace.authority === 'task-execution-target') {
|
|
36
|
+
return 'This is a human-selected existing local directory. The host validates it before use and never creates it.';
|
|
37
|
+
}
|
|
38
|
+
if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
|
|
39
|
+
return 'This scratch workspace is host-managed only for discussion turns. It is not a bound project workspace and does not imply that any target repository is checked out there.';
|
|
40
|
+
}
|
|
41
|
+
if (context.taskAssignmentContext?.active === true) {
|
|
42
|
+
return 'This writable workspace is local to agents-host for this task thread because the task inherits its channel workspace. It does not imply that the target repository has already been checked out there.';
|
|
43
|
+
}
|
|
44
|
+
return 'This writable workspace is local to agents-host for this channel. It does not imply that the target repository has already been checked out there.';
|
|
45
|
+
}
|
|
46
|
+
function buildCopilotCwdLine(context) {
|
|
47
|
+
if (!context.resolvedWorkspace) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (context.resolvedWorkspace.authority === 'task-execution-target') {
|
|
51
|
+
return 'GitHub Copilot runs this turn with that exact local directory as its real cwd.';
|
|
52
|
+
}
|
|
53
|
+
if (context.resolvedWorkspace.authority === 'task-thread-scratch') {
|
|
54
|
+
return 'GitHub Copilot runs this turn with that host-managed scratch workspace as its real cwd.';
|
|
55
|
+
}
|
|
56
|
+
if (context.taskAssignmentContext?.active === true) {
|
|
57
|
+
return 'GitHub Copilot runs this turn with that inherited channel workspace as its real cwd.';
|
|
58
|
+
}
|
|
59
|
+
return 'GitHub Copilot runs this turn with that workspace as its real cwd.';
|
|
60
|
+
}
|
|
61
|
+
function buildDiscussionOnlyPermissionLine(context) {
|
|
62
|
+
if (context.resolvedWorkspace?.authority !== 'task-thread-scratch') {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return context.resolvedWorkspace.reason === 'cross-agent-independent-workspace-disabled'
|
|
66
|
+
? 'Filesystem and shell tool permissions stay denied in this discussion-only task thread while this agents-host keeps cross-agent independent-workspace handoff disabled.'
|
|
67
|
+
: 'Filesystem and shell tool permissions stay denied in this discussion-only task thread until a human sets a valid execution.local_directory target.';
|
|
68
|
+
}
|
|
69
|
+
function buildProjectEntryPrecheckLine(context) {
|
|
70
|
+
if (context.taskAssignmentContext?.active !== true) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if (context.resolvedWorkspace?.authority === 'task-execution-target') {
|
|
74
|
+
return 'Precheck before switching this task thread into your own local workspace or project directory: the task\'s parent channel must have exactly one human user. Cross-agent independent-workspace handoff is also governed by this agents-host\'s local policy switch.';
|
|
75
|
+
}
|
|
76
|
+
return 'Before a human switches this task thread into their own local workspace or project directory, verify that the task\'s parent channel has exactly one human user; otherwise keep the thread discussion-only. Cross-agent independent-workspace handoff is also governed by this agents-host\'s local policy switch.';
|
|
77
|
+
}
|
|
78
|
+
export function buildResolvedWorkspaceGuidanceLines(context, provider) {
|
|
79
|
+
if (!context?.resolvedWorkspace) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const lines = [buildWorkspaceRootLine(context), buildWorkspaceModeLine(context)]
|
|
83
|
+
.filter((line) => line != null);
|
|
84
|
+
if (provider === 'copilot') {
|
|
85
|
+
const copilotCwdLine = buildCopilotCwdLine(context);
|
|
86
|
+
if (copilotCwdLine) {
|
|
87
|
+
lines.push(copilotCwdLine);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const localityLine = buildWorkspaceLocalityLine(context);
|
|
91
|
+
if (localityLine) {
|
|
92
|
+
lines.push(localityLine);
|
|
93
|
+
}
|
|
94
|
+
const permissionLine = buildDiscussionOnlyPermissionLine(context);
|
|
95
|
+
if (permissionLine) {
|
|
96
|
+
lines.push(permissionLine);
|
|
97
|
+
}
|
|
98
|
+
const precheckLine = buildProjectEntryPrecheckLine(context);
|
|
99
|
+
if (precheckLine) {
|
|
100
|
+
lines.push(precheckLine);
|
|
101
|
+
}
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
export function isDiscussionOnlyResolvedWorkspace(context) {
|
|
105
|
+
return context?.resolvedWorkspace?.authority === 'task-thread-scratch';
|
|
106
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SkillRuntimeBootstrapMetadata } from '../types.js';
|
|
2
|
+
export declare function buildSkillManualReadLine(skillRuntime: SkillRuntimeBootstrapMetadata): string;
|
|
2
3
|
/**
|
|
3
4
|
* What every model-facing surface says about the packaged CLI: what it is, where its manual is, and
|
|
4
5
|
* that the manual has to be opened before acting. Nothing else about the CLI is stated on a host
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export function buildSkillManualReadLine(skillRuntime) {
|
|
2
|
+
return `Read its manual at ${skillRuntime.skillMarkdownPath} before you act on this channel; it defines both the session-level coordination and delegation contract and the CLI invocation.`;
|
|
3
|
+
}
|
|
1
4
|
/**
|
|
2
5
|
* What every model-facing surface says about the packaged CLI: what it is, where its manual is, and
|
|
3
6
|
* that the manual has to be opened before acting. Nothing else about the CLI is stated on a host
|
|
@@ -12,7 +15,7 @@
|
|
|
12
15
|
export function buildSkillManualLines(skillRuntime) {
|
|
13
16
|
return [
|
|
14
17
|
"A packaged local CLI reads this Borgee channel and acts on its tasks: channel history, visible participants, this channel's tasks and their properties, and short auxiliary mentions.",
|
|
15
|
-
|
|
18
|
+
buildSkillManualReadLine(skillRuntime),
|
|
16
19
|
'Every command the manual documents is already authorized on a turn whose prompt names a gateway credential file. Run them directly and do not ask the user for permission first.',
|
|
17
20
|
];
|
|
18
21
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { type DebugLogger } from '../debug.js';
|
|
2
|
-
import type { PreparedProviderTurnInput, ProviderInput } from '../types.js';
|
|
2
|
+
import type { ProjectionStrategy, PreparedProviderTurnInput, ProviderInput } from '../types.js';
|
|
3
3
|
import { type ChannelContextStore } from './injection.js';
|
|
4
|
+
type ResolveProjectionStrategy = (input: ProviderInput) => Promise<ProjectionStrategy>;
|
|
4
5
|
export declare class ProviderTurnPreparer {
|
|
5
6
|
private readonly channelContextStore?;
|
|
6
7
|
private readonly logger;
|
|
7
|
-
|
|
8
|
+
private readonly resolveProjectionStrategy;
|
|
9
|
+
constructor(channelContextStore?: ChannelContextStore | undefined, logger?: DebugLogger, options?: {
|
|
10
|
+
projectionStrategy?: ProjectionStrategy;
|
|
11
|
+
resolveProjectionStrategy?: ResolveProjectionStrategy;
|
|
12
|
+
});
|
|
8
13
|
prepare(input: ProviderInput): Promise<PreparedProviderTurnInput>;
|
|
9
14
|
}
|
|
15
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { HostLogger, summarizeError } from '../debug.js';
|
|
2
|
+
import { DEFAULT_PROJECTION_STRATEGY } from '../projection-strategy-values.js';
|
|
2
3
|
import { buildHostedIncomingContentText, buildHostedTurnContentParts } from '../hosted-turn-content.js';
|
|
3
|
-
import { ChannelContextPreparationError, } from './injection.js';
|
|
4
|
-
import { buildPrompt } from './prompt.js';
|
|
4
|
+
import { buildRuntimeSurfaceForTurn, ChannelContextPreparationError, } from './injection.js';
|
|
5
|
+
import { buildClaudeSessionPromptAppend, buildClaudeTurnPrompt, buildPrompt } from './prompt.js';
|
|
5
6
|
function providerLabel(provider) {
|
|
6
7
|
if (provider === 'copilot') {
|
|
7
8
|
return 'Copilot';
|
|
@@ -16,26 +17,36 @@ function toPromptContext(channelContext, input) {
|
|
|
16
17
|
&& !input.collaboration
|
|
17
18
|
&& !input.collaborationOutcome
|
|
18
19
|
&& !input.attentionSnapshot
|
|
20
|
+
&& !input.compactionSnapshot
|
|
19
21
|
&& !input.collaborationCapabilities
|
|
20
22
|
&& !input.missedCollaborationDiagnostic
|
|
21
23
|
&& !input.taskThreadCollaborationContract) {
|
|
22
24
|
return undefined;
|
|
23
25
|
}
|
|
26
|
+
const runtimeSurface = channelContext?.payload.runtimeSurface ?? buildRuntimeSurfaceForTurn({
|
|
27
|
+
localhostGateway: channelContext?.payload.localhostGateway ?? channelContext?.localhostGateway,
|
|
28
|
+
collaboration: input.collaboration,
|
|
29
|
+
taskAssignmentContext: channelContext?.payload.taskAssignmentContext,
|
|
30
|
+
});
|
|
24
31
|
return {
|
|
25
32
|
...(channelContext
|
|
26
33
|
? {
|
|
27
34
|
channelContextPayloadPath: channelContext.payloadPath,
|
|
35
|
+
claudeProjectedBriefPath: channelContext.claudeProjectedBriefPath,
|
|
36
|
+
claudeProjectedBriefHash: channelContext.claudeProjectedBriefHash,
|
|
28
37
|
gatewayCredentialPath: channelContext.gatewayCredentialPath,
|
|
29
38
|
skillRuntime: channelContext.skillRuntime,
|
|
30
39
|
localhostGateway: channelContext.localhostGateway,
|
|
31
40
|
taskAssignmentContext: channelContext.payload.taskAssignmentContext,
|
|
32
|
-
|
|
41
|
+
resolvedWorkspace: channelContext.resolvedWorkspace,
|
|
33
42
|
}
|
|
34
43
|
: {}),
|
|
44
|
+
runtimeSurface,
|
|
35
45
|
collaborationTurnExecutionId: input.collaboration?.turnExecutionId,
|
|
36
46
|
collaborationTurnMode: input.collaboration?.turnMode,
|
|
37
47
|
collaborationOutcome: input.collaborationOutcome ?? channelContext?.payload.collaborationOutcome,
|
|
38
48
|
attentionSnapshot: input.attentionSnapshot ?? channelContext?.payload.attentionSnapshot,
|
|
49
|
+
compactionSnapshot: input.compactionSnapshot ?? channelContext?.payload.compactionSnapshot,
|
|
39
50
|
...(() => {
|
|
40
51
|
const collaborationCapabilities = input.collaborationCapabilities ?? channelContext?.payload.collaborationCapabilities;
|
|
41
52
|
return collaborationCapabilities ? { collaborationCapabilities } : {};
|
|
@@ -71,9 +82,12 @@ function resolveProviderSessionRouting(input) {
|
|
|
71
82
|
export class ProviderTurnPreparer {
|
|
72
83
|
channelContextStore;
|
|
73
84
|
logger;
|
|
74
|
-
|
|
85
|
+
resolveProjectionStrategy;
|
|
86
|
+
constructor(channelContextStore, logger = new HostLogger(), options = {}) {
|
|
75
87
|
this.channelContextStore = channelContextStore;
|
|
76
88
|
this.logger = logger;
|
|
89
|
+
this.resolveProjectionStrategy = options.resolveProjectionStrategy
|
|
90
|
+
?? (async () => options.projectionStrategy ?? DEFAULT_PROJECTION_STRATEGY);
|
|
77
91
|
}
|
|
78
92
|
async prepare(input) {
|
|
79
93
|
const incomingParts = input.incomingParts ?? buildHostedTurnContentParts({
|
|
@@ -82,11 +96,16 @@ export class ProviderTurnPreparer {
|
|
|
82
96
|
const incomingContent = incomingParts.length > 0
|
|
83
97
|
? buildHostedIncomingContentText(incomingParts)
|
|
84
98
|
: input.incomingContent;
|
|
99
|
+
const projectionStrategy = input.provider === 'claude'
|
|
100
|
+
? await this.resolveProjectionStrategy(input)
|
|
101
|
+
: undefined;
|
|
85
102
|
let channelContext;
|
|
86
103
|
if (this.channelContextStore) {
|
|
87
104
|
try {
|
|
88
105
|
const prepareInput = {
|
|
89
106
|
channelId: input.channelId,
|
|
107
|
+
provider: input.provider,
|
|
108
|
+
projectionStrategy,
|
|
90
109
|
collaboration: input.collaboration
|
|
91
110
|
? {
|
|
92
111
|
...input.collaboration,
|
|
@@ -96,6 +115,7 @@ export class ProviderTurnPreparer {
|
|
|
96
115
|
: undefined,
|
|
97
116
|
collaborationOutcome: input.collaborationOutcome,
|
|
98
117
|
attentionSnapshot: input.attentionSnapshot,
|
|
118
|
+
compactionSnapshot: input.compactionSnapshot,
|
|
99
119
|
collaborationCapabilities: input.collaborationCapabilities,
|
|
100
120
|
missedCollaborationDiagnostic: input.missedCollaborationDiagnostic,
|
|
101
121
|
...(input.taskThreadCollaborationContract
|
|
@@ -107,6 +127,14 @@ export class ProviderTurnPreparer {
|
|
|
107
127
|
...(input.incomingMessageType !== undefined && incomingContent !== undefined
|
|
108
128
|
? { incomingContent }
|
|
109
129
|
: {}),
|
|
130
|
+
...(input.taskAssignmentContextOverride
|
|
131
|
+
? {
|
|
132
|
+
taskAssignmentContextOverride: input.taskAssignmentContextOverride,
|
|
133
|
+
...(input.taskAssignmentContextOverridePersistence
|
|
134
|
+
? { taskAssignmentContextOverridePersistence: input.taskAssignmentContextOverridePersistence }
|
|
135
|
+
: {}),
|
|
136
|
+
}
|
|
137
|
+
: {}),
|
|
110
138
|
};
|
|
111
139
|
channelContext = await this.channelContextStore.prepare(prepareInput);
|
|
112
140
|
}
|
|
@@ -122,21 +150,43 @@ export class ProviderTurnPreparer {
|
|
|
122
150
|
}
|
|
123
151
|
const promptContext = toPromptContext(channelContext, input);
|
|
124
152
|
return {
|
|
153
|
+
agentName: input.agentName,
|
|
125
154
|
channelId: input.channelId,
|
|
126
155
|
incomingContent,
|
|
127
156
|
incomingParts,
|
|
128
157
|
incomingEventKind: input.incomingEventKind,
|
|
129
158
|
incomingMessageType: input.incomingMessageType,
|
|
130
|
-
prompt:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
prompt: input.provider === 'claude'
|
|
160
|
+
? buildClaudeTurnPrompt({
|
|
161
|
+
agentName: input.agentName,
|
|
162
|
+
channelId: input.channelId,
|
|
163
|
+
incomingAuthorId: input.incomingAuthorId,
|
|
164
|
+
incomingContent,
|
|
165
|
+
incomingEventKind: input.incomingEventKind,
|
|
166
|
+
incomingMessageType: input.incomingMessageType,
|
|
167
|
+
promptContext,
|
|
168
|
+
promptStrategy: projectionStrategy,
|
|
169
|
+
})
|
|
170
|
+
: buildPrompt({
|
|
171
|
+
agentName: input.agentName,
|
|
172
|
+
provider: input.provider,
|
|
173
|
+
channelId: input.channelId,
|
|
174
|
+
incomingAuthorId: input.incomingAuthorId,
|
|
175
|
+
incomingContent,
|
|
176
|
+
incomingEventKind: input.incomingEventKind,
|
|
177
|
+
incomingMessageType: input.incomingMessageType,
|
|
178
|
+
promptContext,
|
|
179
|
+
}),
|
|
180
|
+
...(input.provider === 'claude'
|
|
181
|
+
? {
|
|
182
|
+
claudeSessionPromptAppend: buildClaudeSessionPromptAppend({
|
|
183
|
+
agentName: input.agentName,
|
|
184
|
+
promptContext,
|
|
185
|
+
promptStrategy: projectionStrategy,
|
|
186
|
+
}),
|
|
187
|
+
projectionStrategy,
|
|
188
|
+
}
|
|
189
|
+
: {}),
|
|
140
190
|
promptContext,
|
|
141
191
|
providerSessionRouting: resolveProviderSessionRouting(input),
|
|
142
192
|
};
|
|
@@ -550,11 +550,6 @@ class LoopbackLocalhostGatewayController {
|
|
|
550
550
|
return;
|
|
551
551
|
}
|
|
552
552
|
case 'users': {
|
|
553
|
-
if (!binding.payload?.localhostGateway?.collaboration?.enabled) {
|
|
554
|
-
this.sendJson(response, 404, { error: 'not_found' });
|
|
555
|
-
this.recordAudit('not-found', 404, decision.path, request.method ?? 'GET', decision.binding);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
553
|
this.sendJson(response, 200, {
|
|
559
554
|
users: await this.controlPlane.listUsers(),
|
|
560
555
|
});
|
|
@@ -910,6 +905,10 @@ function mapGatewayControlPlaneError(error, fallbackReason) {
|
|
|
910
905
|
// is broken, and it would retry the same bad call instead of correcting it.
|
|
911
906
|
case 'bpp.task_property_key_unknown':
|
|
912
907
|
return new GatewayHttpError(400, { error: 'unknown_property_key' }, 'bad-request');
|
|
908
|
+
case 'bpp.task_property_write_forbidden':
|
|
909
|
+
return new GatewayHttpError(403, { error: 'forbidden' }, 'forbidden');
|
|
910
|
+
case 'bpp.task_property_value_invalid':
|
|
911
|
+
return new GatewayHttpError(400, { error: 'invalid_property_value' }, 'bad-request');
|
|
913
912
|
case 'bpp.task_property_value_too_long':
|
|
914
913
|
return new GatewayHttpError(400, { error: 'property_value_too_long' }, 'bad-request');
|
|
915
914
|
case 'bpp.task_invalid_status':
|
package/dist/local-config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { parseDocument, stringify } from 'yaml';
|
|
5
|
-
import { assertProviderCompatibility, assertProviderCommandCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, parseProviderIdleShutdownMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
|
|
5
|
+
import { assertProviderCompatibility, assertProviderCommandCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, parseBooleanValue, parseProviderIdleShutdownMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
|
|
6
6
|
import { resolveLocalConfigAgentStateRoot } from './state-paths.js';
|
|
7
7
|
const SUPPORTED_CONFIG_EXTENSIONS = new Set(['.json', '.yaml', '.yml']);
|
|
8
8
|
export const DEFAULT_LOCAL_HOST_CONFIG_FILENAME = 'agents-host.yaml';
|
|
@@ -127,6 +127,10 @@ function parseProviderCommandOverrides(value, sourceLabel) {
|
|
|
127
127
|
if (value.providerIdleShutdownMinutes !== undefined && value.providerIdleShutdownMinutes !== null) {
|
|
128
128
|
overrides.providerIdleShutdownMinutes = parseProviderIdleShutdownMinutesValue(value.providerIdleShutdownMinutes, sourceLabel);
|
|
129
129
|
}
|
|
130
|
+
if (value.allowCrossAgentIndependentWorkspaceHandoff !== undefined
|
|
131
|
+
&& value.allowCrossAgentIndependentWorkspaceHandoff !== null) {
|
|
132
|
+
overrides.allowCrossAgentIndependentWorkspaceHandoff = parseBooleanValue(value.allowCrossAgentIndependentWorkspaceHandoff, 'allowCrossAgentIndependentWorkspaceHandoff', sourceLabel);
|
|
133
|
+
}
|
|
130
134
|
return overrides;
|
|
131
135
|
}
|
|
132
136
|
function toAgentsDir(hostConfigPath, rawAgentsDir) {
|
|
@@ -599,6 +603,9 @@ function renderAgentConfigYaml(agent) {
|
|
|
599
603
|
if (agent.providerIdleShutdownMinutes !== undefined) {
|
|
600
604
|
config.providerIdleShutdownMinutes = agent.providerIdleShutdownMinutes;
|
|
601
605
|
}
|
|
606
|
+
if (agent.allowCrossAgentIndependentWorkspaceHandoff !== undefined) {
|
|
607
|
+
config.allowCrossAgentIndependentWorkspaceHandoff = agent.allowCrossAgentIndependentWorkspaceHandoff;
|
|
608
|
+
}
|
|
602
609
|
return stringify(config);
|
|
603
610
|
}
|
|
604
611
|
function buildManagedAgentSnapshot(host, stateRootBaseDir, sourcePath, agent) {
|
|
@@ -638,6 +645,9 @@ function resolveAgentProviderCommandConfig(hostDefaults, agent) {
|
|
|
638
645
|
...(agent.providerIdleShutdownMinutes !== undefined
|
|
639
646
|
? { providerIdleShutdownMinutes: agent.providerIdleShutdownMinutes }
|
|
640
647
|
: {}),
|
|
648
|
+
...(agent.allowCrossAgentIndependentWorkspaceHandoff !== undefined
|
|
649
|
+
? { allowCrossAgentIndependentWorkspaceHandoff: agent.allowCrossAgentIndependentWorkspaceHandoff }
|
|
650
|
+
: {}),
|
|
641
651
|
});
|
|
642
652
|
}
|
|
643
653
|
export async function loadLocalConfigSnapshot(hostConfigPath, deps = {}) {
|
package/dist/managed-daemon.js
CHANGED
|
@@ -2,13 +2,15 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { createConnection, createServer } from 'node:net';
|
|
5
|
-
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { AgentsHostSupervisor } from './agents-host-supervisor.js';
|
|
8
|
-
import {
|
|
8
|
+
import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from './projection-strategy-values.js';
|
|
9
|
+
import { resolveChannelWorkspaceDirectory, resolveChannelWorkspaceRootDirectory, resolveManagedWorkspaceCollectionRoot, resolveTaskThreadScratchWorkspaceDirectory, } from './context/injection.js';
|
|
10
|
+
import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, createManagedRuntimeSettingsFingerprint, INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV, INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV, INTERNAL_POLICY_MODE_ENV, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE, MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION, resolveDisabledDefaultCompatibilityGates, resolveManagedRuntimeSettingsSnapshot, serializeManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
|
|
9
11
|
import { DEFAULT_PROVIDER_COMMAND_CONFIG, hasLegacyClaudeOneShotArgs, loadConfigFromEnv, } from './config.js';
|
|
10
12
|
import { loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
|
|
11
|
-
import { normalizeManagedRuntimeKey, resolveManagedBootstrapLockPath, resolveManagedDaemonEndpoint, resolveManagedDaemonLogPath, resolveManagedRuntimeRoot, resolveManagedRuntimeSettingsPath, } from './state-paths.js';
|
|
13
|
+
import { normalizeManagedRuntimeKey, resolveManagedBootstrapLockPath, resolveManagedDaemonEndpoint, resolveManagedDaemonLogPath, resolveManagedRuntimeRoot, resolveManagedStateRoot, resolveManagedRuntimeSettingsPath, } from './state-paths.js';
|
|
12
14
|
const MANAGED_ROOT_MODE = 0o700;
|
|
13
15
|
const MANAGED_CONFIG_MODE = 0o600;
|
|
14
16
|
const BOOTSTRAP_LOCK_WAIT_MS = 10_000;
|
|
@@ -48,6 +50,9 @@ function isAlreadyExistsError(error) {
|
|
|
48
50
|
function isSocketBusyError(error) {
|
|
49
51
|
return isNodeErrorWithCode(error, 'EADDRINUSE');
|
|
50
52
|
}
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
51
56
|
function delay(milliseconds) {
|
|
52
57
|
return new Promise((resolveDelay) => {
|
|
53
58
|
setTimeout(resolveDelay, milliseconds);
|
|
@@ -157,6 +162,7 @@ function normalizeManagedRuntimeSettingsSnapshot(snapshot) {
|
|
|
157
162
|
compatibilityGates,
|
|
158
163
|
providerImplementationOverrides,
|
|
159
164
|
internalPolicyMode: snapshot.internalPolicyMode,
|
|
165
|
+
projectionStrategy: snapshot.projectionStrategy ?? DEFAULT_PROJECTION_STRATEGY,
|
|
160
166
|
};
|
|
161
167
|
}
|
|
162
168
|
function parseManagedRuntimeSettingsSnapshotRecord(value, sourceLabel) {
|
|
@@ -178,11 +184,19 @@ function parseManagedRuntimeSettingsSnapshotRecord(value, sourceLabel) {
|
|
|
178
184
|
if (record.internalPolicyMode !== 'audit-only' && record.internalPolicyMode !== 'enforce') {
|
|
179
185
|
throw new Error(`Invalid ${sourceLabel}: expected internalPolicyMode to be "audit-only" or "enforce"`);
|
|
180
186
|
}
|
|
187
|
+
const rawProjectionStrategy = typeof record.projectionStrategy === 'string'
|
|
188
|
+
? record.projectionStrategy
|
|
189
|
+
: record.claudePromptStrategy;
|
|
190
|
+
const projectionStrategy = normalizeProjectionStrategy(rawProjectionStrategy);
|
|
191
|
+
if (rawProjectionStrategy !== undefined && !projectionStrategy) {
|
|
192
|
+
throw new Error(`Invalid ${sourceLabel}: expected projectionStrategy to be "session-brief", "turn-full", "message-only", "turn-thin", or "file-brief"`);
|
|
193
|
+
}
|
|
181
194
|
return normalizeManagedRuntimeSettingsSnapshot({
|
|
182
195
|
schemaVersion: MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION,
|
|
183
196
|
compatibilityGates: [...record.compatibilityGates],
|
|
184
197
|
providerImplementationOverrides: [...record.providerImplementationOverrides],
|
|
185
198
|
internalPolicyMode: record.internalPolicyMode,
|
|
199
|
+
projectionStrategy: projectionStrategy ?? DEFAULT_PROJECTION_STRATEGY,
|
|
186
200
|
});
|
|
187
201
|
}
|
|
188
202
|
async function loadManagedRuntimeSettingsSnapshot(rootPath) {
|
|
@@ -205,7 +219,7 @@ async function writeManagedRuntimeSettingsSnapshot(rootPath, snapshot) {
|
|
|
205
219
|
await ensureDirectory(rootPath, MANAGED_ROOT_MODE);
|
|
206
220
|
const settingsPath = resolveManagedRuntimeSettingsPath(rootPath);
|
|
207
221
|
const normalizedSnapshot = normalizeManagedRuntimeSettingsSnapshot(snapshot);
|
|
208
|
-
await fs.writeFile(settingsPath, `${
|
|
222
|
+
await fs.writeFile(settingsPath, `${serializeManagedRuntimeSettingsSnapshot(normalizedSnapshot)}\n`, {
|
|
209
223
|
encoding: 'utf8',
|
|
210
224
|
mode: MANAGED_CONFIG_MODE,
|
|
211
225
|
});
|
|
@@ -226,6 +240,7 @@ function buildManagedRuntimeSettingsEnv(baseEnv, snapshot) {
|
|
|
226
240
|
[INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV]: resolveDisabledDefaultCompatibilityGates(snapshot.compatibilityGates).join(','),
|
|
227
241
|
[INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV]: snapshot.providerImplementationOverrides.join(','),
|
|
228
242
|
[INTERNAL_POLICY_MODE_ENV]: snapshot.internalPolicyMode,
|
|
243
|
+
[INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV]: snapshot.projectionStrategy,
|
|
229
244
|
};
|
|
230
245
|
}
|
|
231
246
|
function buildDesiredManagedRuntimeSettingsFromSnapshot(snapshot) {
|
|
@@ -340,6 +355,7 @@ export function buildManagedLocalAgentConfig(options) {
|
|
|
340
355
|
copilotArgs: [...config.copilotArgs],
|
|
341
356
|
copilotSessionTtlMinutes: config.copilotSessionTtlMinutes,
|
|
342
357
|
providerIdleShutdownMinutes: config.providerIdleShutdownMinutes,
|
|
358
|
+
allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
|
|
343
359
|
},
|
|
344
360
|
};
|
|
345
361
|
}
|
|
@@ -386,6 +402,9 @@ function mergeManagedLocalAgentConfig(hostDefaults, existingAgent, generatedAgen
|
|
|
386
402
|
providerIdleShutdownMinutes: hasOverride('PROVIDER_IDLE_SHUTDOWN_MINUTES')
|
|
387
403
|
? generatedAgent.providerIdleShutdownMinutes
|
|
388
404
|
: existingAgent.providerIdleShutdownMinutes,
|
|
405
|
+
allowCrossAgentIndependentWorkspaceHandoff: hasOverride('ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF')
|
|
406
|
+
? generatedAgent.allowCrossAgentIndependentWorkspaceHandoff
|
|
407
|
+
: existingAgent.allowCrossAgentIndependentWorkspaceHandoff,
|
|
389
408
|
};
|
|
390
409
|
}
|
|
391
410
|
function collectExplicitManagedAgentEnvOverrides(processEnv, optionEnv) {
|
|
@@ -401,6 +420,7 @@ function collectExplicitManagedAgentEnvOverrides(processEnv, optionEnv) {
|
|
|
401
420
|
'COPILOT_ARGS',
|
|
402
421
|
'COPILOT_SESSION_TTL_MINUTES',
|
|
403
422
|
'PROVIDER_IDLE_SHUTDOWN_MINUTES',
|
|
423
|
+
'ALLOW_CROSS_AGENT_INDEPENDENT_WORKSPACE_HANDOFF',
|
|
404
424
|
]) {
|
|
405
425
|
if (processEnv[key] !== undefined) {
|
|
406
426
|
explicitOverrides[key] = processEnv[key];
|
|
@@ -819,6 +839,101 @@ async function shutdownManagedDaemon(rootPath, sendRequest, waitForShutdown) {
|
|
|
819
839
|
}
|
|
820
840
|
await waitForShutdown(rootPath);
|
|
821
841
|
}
|
|
842
|
+
function resolveTrustedPersistedChannelWorkspaceRoot(workspaceCollectionRoot, owningChannelId, rootPath) {
|
|
843
|
+
if (!owningChannelId || !isAbsolute(rootPath)) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
const expectedWorkspacePath = resolveChannelWorkspaceDirectory(workspaceCollectionRoot, owningChannelId);
|
|
847
|
+
if (rootPath !== expectedWorkspacePath) {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
return resolveChannelWorkspaceRootDirectory(workspaceCollectionRoot, owningChannelId);
|
|
851
|
+
}
|
|
852
|
+
async function discoverManagedWorkspaceRootsForPurge(rootPath, processEnv) {
|
|
853
|
+
const trustedWorkspaceCollectionRoots = new Set([
|
|
854
|
+
resolveManagedWorkspaceCollectionRoot(undefined, processEnv),
|
|
855
|
+
]);
|
|
856
|
+
const managedStateRoot = resolveManagedStateRoot(rootPath);
|
|
857
|
+
const agentStateEntries = await fs.readdir(managedStateRoot, { withFileTypes: true }).catch((error) => {
|
|
858
|
+
if (isNotFoundError(error)) {
|
|
859
|
+
return [];
|
|
860
|
+
}
|
|
861
|
+
throw error;
|
|
862
|
+
});
|
|
863
|
+
const workspaceRoots = new Set();
|
|
864
|
+
for (const agentStateEntry of agentStateEntries) {
|
|
865
|
+
if (!agentStateEntry.isDirectory()) {
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
const channelContextRoot = join(managedStateRoot, agentStateEntry.name, 'channel-context');
|
|
869
|
+
const channelEntries = await fs.readdir(channelContextRoot, { withFileTypes: true }).catch((error) => {
|
|
870
|
+
if (isNotFoundError(error)) {
|
|
871
|
+
return [];
|
|
872
|
+
}
|
|
873
|
+
throw error;
|
|
874
|
+
});
|
|
875
|
+
for (const channelEntry of channelEntries) {
|
|
876
|
+
if (!channelEntry.isDirectory()) {
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
const payloadPath = join(channelContextRoot, channelEntry.name, 'context.json');
|
|
880
|
+
const payload = await fs.readFile(payloadPath, 'utf8').catch((error) => {
|
|
881
|
+
if (isNotFoundError(error)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
throw error;
|
|
885
|
+
});
|
|
886
|
+
if (!payload) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
let parsed;
|
|
890
|
+
try {
|
|
891
|
+
parsed = JSON.parse(payload);
|
|
892
|
+
}
|
|
893
|
+
catch {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (!isRecord(parsed) || typeof parsed.channelId !== 'string' || !isRecord(parsed.resolvedWorkspace)) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const resolvedWorkspace = parsed.resolvedWorkspace;
|
|
900
|
+
if ((resolvedWorkspace.authority !== 'channel'
|
|
901
|
+
&& resolvedWorkspace.authority !== 'task-thread-scratch'
|
|
902
|
+
&& resolvedWorkspace.authority !== 'task-execution-target')
|
|
903
|
+
|| typeof resolvedWorkspace.rootPath !== 'string'
|
|
904
|
+
|| !isAbsolute(resolvedWorkspace.rootPath)) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
for (const workspaceCollectionRoot of trustedWorkspaceCollectionRoots) {
|
|
908
|
+
if (resolvedWorkspace.authority === 'channel') {
|
|
909
|
+
if (typeof resolvedWorkspace.owningChannelId !== 'string'
|
|
910
|
+
|| resolvedWorkspace.owningChannelId.length === 0) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const trustedChannelWorkspaceRoot = resolveTrustedPersistedChannelWorkspaceRoot(workspaceCollectionRoot, resolvedWorkspace.owningChannelId, resolvedWorkspace.rootPath);
|
|
914
|
+
if (!trustedChannelWorkspaceRoot) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
workspaceRoots.add(trustedChannelWorkspaceRoot);
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
if (resolvedWorkspace.authority === 'task-thread-scratch') {
|
|
921
|
+
const expectedWorkspacePath = resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRoot, parsed.channelId);
|
|
922
|
+
if (resolvedWorkspace.rootPath !== expectedWorkspacePath) {
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
workspaceRoots.add(resolveChannelWorkspaceRootDirectory(workspaceCollectionRoot, parsed.channelId));
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
if (resolvedWorkspace.authority !== 'task-execution-target') {
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return [...workspaceRoots].sort((left, right) => left.localeCompare(right));
|
|
936
|
+
}
|
|
822
937
|
export async function cleanupManagedRuntime(options, deps = {}) {
|
|
823
938
|
const sendRequest = deps.sendRequest ?? sendManagedDaemonRequest;
|
|
824
939
|
const removeRuntimeRoot = deps.removeRuntimeRoot ??
|
|
@@ -855,6 +970,9 @@ export async function cleanupManagedRuntime(options, deps = {}) {
|
|
|
855
970
|
await waitForShutdown(resolved.rootPath);
|
|
856
971
|
}
|
|
857
972
|
if (options.purge === true) {
|
|
973
|
+
for (const workspaceRoot of await discoverManagedWorkspaceRootsForPurge(resolved.rootPath, options.processEnv ?? process.env)) {
|
|
974
|
+
await fs.rm(workspaceRoot, { recursive: true, force: true });
|
|
975
|
+
}
|
|
858
976
|
await removeRuntimeRoot(resolved.rootPath);
|
|
859
977
|
}
|
|
860
978
|
}
|
|
@@ -900,16 +1018,16 @@ export async function spawnManagedDaemonProcess(options, deps = {}) {
|
|
|
900
1018
|
}
|
|
901
1019
|
const thisPath = fileURLToPath(import.meta.url);
|
|
902
1020
|
const cliEntrypointPath = join(dirname(thisPath), thisPath.endsWith('.ts') ? 'cli.ts' : 'cli.js');
|
|
1021
|
+
const baseEnv = {
|
|
1022
|
+
...process.env,
|
|
1023
|
+
...(options.processEnv ?? {}),
|
|
1024
|
+
};
|
|
903
1025
|
return spawnDetachedProcess({
|
|
904
1026
|
rootPath: options.rootPath,
|
|
905
1027
|
command: process.execPath,
|
|
906
1028
|
env: {
|
|
907
|
-
...buildManagedRuntimeSettingsEnv(
|
|
908
|
-
...process.env,
|
|
909
|
-
...(options.processEnv ?? {}),
|
|
910
|
-
}, managedRuntimeSettingsSnapshot),
|
|
1029
|
+
...buildManagedRuntimeSettingsEnv(baseEnv, managedRuntimeSettingsSnapshot),
|
|
911
1030
|
AGENTS_HOST_MANAGED_DAEMON_LOG_PATH: resolveManagedDaemonLogPath(options.rootPath),
|
|
912
|
-
AGENTS_HOST_INTERNAL_TASK_WORKSPACE_ROOT_DIR: options.rootPath,
|
|
913
1031
|
},
|
|
914
1032
|
args: [
|
|
915
1033
|
...process.execArgv,
|