@borgee/agents-host 0.2.56 → 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 +11 -10
- package/dist/agents-host.d.ts +1 -0
- package/dist/agents-host.js +103 -9
- package/dist/compatibility-gates.js +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +21 -0
- package/dist/context/injection.d.ts +20 -2
- package/dist/context/injection.js +146 -103
- package/dist/context/prompt.js +11 -7
- package/dist/context/resolved-workspace.d.ts +1 -0
- package/dist/context/resolved-workspace.js +50 -8
- package/dist/context/turn-preparation.js +8 -0
- package/dist/gateway/localhost-gateway.js +2 -5
- package/dist/local-config.js +11 -1
- package/dist/managed-daemon.js +18 -13
- package/dist/plugin-sdk.js +0 -17
- package/dist/plugin-sdk.js.map +2 -2
- 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/providers/claude/cli-client.d.ts +1 -1
- package/dist/providers/claude/cli-client.js +30 -3
- package/dist/providers/codex/cli-client.d.ts +2 -0
- package/dist/providers/codex/cli-client.js +25 -21
- package/dist/providers/copilot/cli-client.d.ts +1 -1
- package/dist/providers/copilot/cli-client.js +9 -3
- package/dist/providers/create-provider.d.ts +1 -1
- package/dist/providers/create-provider.js +2 -0
- package/dist/types.d.ts +16 -3
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +4 -4
- package/skills/borgee-agent/references/errors.md +2 -2
- package/skills/borgee-agent/references/task-properties.md +4 -3
|
@@ -10,6 +10,7 @@ export interface CopilotPermissionPolicyContext {
|
|
|
10
10
|
auditSink?: AuthorizationAuditSinkLike;
|
|
11
11
|
agentId?: string;
|
|
12
12
|
channelId?: string;
|
|
13
|
+
toolPermissionMode?: 'default' | 'deny-all';
|
|
13
14
|
}
|
|
14
15
|
export declare function resolveCopilotPermissionResponse(context: CopilotPermissionPolicyContext): RequestPermissionResponse;
|
|
15
16
|
export declare function createLegacyMissingAllowOptionError(): Error;
|
|
@@ -84,6 +84,24 @@ function appendAuditRecord(context, effectiveDecision, reason, policyDecision) {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
export function resolveCopilotPermissionResponse(context) {
|
|
87
|
+
if (context.toolPermissionMode === 'deny-all') {
|
|
88
|
+
const rejectOption = resolveRejectOption(context.params.options);
|
|
89
|
+
const deniedDecision = rejectOption
|
|
90
|
+
? {
|
|
91
|
+
outcome: 'selected',
|
|
92
|
+
optionId: rejectOption.optionId,
|
|
93
|
+
optionKind: rejectOption.kind,
|
|
94
|
+
reason: 'discussion-only-task-thread',
|
|
95
|
+
toolKind: normalizeToolKind(context.params.toolCall.kind),
|
|
96
|
+
}
|
|
97
|
+
: {
|
|
98
|
+
outcome: 'cancelled',
|
|
99
|
+
reason: 'discussion-only-task-thread',
|
|
100
|
+
toolKind: normalizeToolKind(context.params.toolCall.kind),
|
|
101
|
+
};
|
|
102
|
+
appendAuditRecord(context, deniedDecision, deniedDecision.reason, deniedDecision);
|
|
103
|
+
return decisionToResponse(deniedDecision);
|
|
104
|
+
}
|
|
87
105
|
if (!context.gateEnabled) {
|
|
88
106
|
const legacyOption = legacySelectPermissionOption(context.params.options);
|
|
89
107
|
return {
|
|
@@ -33,7 +33,7 @@ function decodePathSegment(segment) {
|
|
|
33
33
|
export function matchChannelRoute(pathname, allowCollaborationRoutes = false) {
|
|
34
34
|
const resources = allowCollaborationRoutes
|
|
35
35
|
? '(bootstrap|me|history|draft|users|messages|tasks|current-task)'
|
|
36
|
-
: '(bootstrap|me|history|tasks|current-task)';
|
|
36
|
+
: '(bootstrap|me|history|users|tasks|current-task)';
|
|
37
37
|
const match = new RegExp(`^/v1/channels/([^/]+)/${resources}$`).exec(pathname);
|
|
38
38
|
if (!match) {
|
|
39
39
|
return null;
|
|
@@ -112,7 +112,7 @@ function allowedMethodsForRoute(route, collaborationRoutesEnabled) {
|
|
|
112
112
|
export function evaluateGatewayAuthorization(input) {
|
|
113
113
|
const url = new URL(input.request.url ?? '/', input.baseUrl);
|
|
114
114
|
const hiddenCollaborationRoute = !input.allowCollaborationRoutes
|
|
115
|
-
&& ['draft', '
|
|
115
|
+
&& ['draft', 'messages'].includes(matchChannelRoute(url.pathname, true)?.resource ?? '');
|
|
116
116
|
if (hasVisibleHeader(input.request.headers.origin)) {
|
|
117
117
|
return {
|
|
118
118
|
reason: 'browser-origin-not-allowed',
|
|
@@ -93,7 +93,7 @@ export declare class ClaudeCliClient {
|
|
|
93
93
|
private invalidateSession;
|
|
94
94
|
private closeSession;
|
|
95
95
|
private rejectQueuedTurnsAfterSessionTaint;
|
|
96
|
-
private
|
|
96
|
+
private findChannelStateBySessionId;
|
|
97
97
|
private currentSessionStoreAgentId;
|
|
98
98
|
private ensureSessionStoreLoaded;
|
|
99
99
|
private readPersistedSessionId;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Readable, Writable } from 'node:stream';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import { dirname, resolve } from 'node:path';
|
|
5
6
|
import spawn from 'cross-spawn';
|
|
@@ -11,6 +12,7 @@ import { AcpProgressCollector } from '../acp-progress-collector.js';
|
|
|
11
12
|
import { readClaudeActivityMetadata } from './activity-metadata.js';
|
|
12
13
|
import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
|
|
13
14
|
import { buildClaudeSessionPromptAppend } from '../../context/prompt.js';
|
|
15
|
+
import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
|
|
14
16
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
15
17
|
const SESSION_TAINTED_ERRORS = new WeakSet();
|
|
16
18
|
const IDLE_BACKEND_STOPPED_MESSAGE = 'Claude ACP backend stopped after idle timeout';
|
|
@@ -386,6 +388,22 @@ function hashSessionAppend(systemPromptAppend) {
|
|
|
386
388
|
}
|
|
387
389
|
return createHash('sha256').update(systemPromptAppend).digest('hex');
|
|
388
390
|
}
|
|
391
|
+
function resolveSkillManualContentHash(promptContext) {
|
|
392
|
+
const skillMarkdownPath = promptContext?.skillRuntime?.skillMarkdownPath;
|
|
393
|
+
if (!skillMarkdownPath) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
return createHash('sha256')
|
|
398
|
+
.update(skillMarkdownPath)
|
|
399
|
+
.update('\0')
|
|
400
|
+
.update(readFileSync(skillMarkdownPath))
|
|
401
|
+
.digest('hex');
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return `unreadable:${skillMarkdownPath}`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
389
407
|
function resolveSessionVisibilityKey(promptContext, systemPromptAppend, promptStrategy) {
|
|
390
408
|
const resolvedPromptStrategy = resolveProjectionStrategy(promptStrategy);
|
|
391
409
|
const explicitPromptStrategy = resolvedPromptStrategy === DEFAULT_PROJECTION_STRATEGY && promptStrategy == null
|
|
@@ -395,9 +413,11 @@ function resolveSessionVisibilityKey(promptContext, systemPromptAppend, promptSt
|
|
|
395
413
|
: resolvedPromptStrategy;
|
|
396
414
|
const additionalDirectories = resolveSessionAdditionalDirectories(promptContext);
|
|
397
415
|
const sessionAppendHash = hashSessionAppend(systemPromptAppend);
|
|
416
|
+
const skillManualContentHash = resolveSkillManualContentHash(promptContext);
|
|
398
417
|
const projectedBriefHash = promptContext?.claudeProjectedBriefHash;
|
|
399
418
|
if (!additionalDirectories
|
|
400
419
|
&& !sessionAppendHash
|
|
420
|
+
&& !skillManualContentHash
|
|
401
421
|
&& !projectedBriefHash
|
|
402
422
|
&& !promptContext?.gatewayCredentialPath
|
|
403
423
|
&& !explicitPromptStrategy) {
|
|
@@ -407,12 +427,14 @@ function resolveSessionVisibilityKey(promptContext, systemPromptAppend, promptSt
|
|
|
407
427
|
return JSON.stringify({
|
|
408
428
|
...(additionalDirectories ? { additionalDirectories } : {}),
|
|
409
429
|
sessionAppendHash,
|
|
430
|
+
...(skillManualContentHash ? { skillManualContentHash } : {}),
|
|
410
431
|
...(projectedBriefHash ? { projectedBriefHash } : {}),
|
|
411
432
|
...(explicitPromptStrategy ? { promptStrategy: explicitPromptStrategy } : {}),
|
|
412
433
|
});
|
|
413
434
|
}
|
|
414
435
|
return JSON.stringify({
|
|
415
436
|
...(additionalDirectories ? { additionalDirectories } : {}),
|
|
437
|
+
...(skillManualContentHash ? { skillManualContentHash } : {}),
|
|
416
438
|
...(promptContext?.gatewayCredentialPath
|
|
417
439
|
? { gatewayCredentialPath: promptContext.gatewayCredentialPath }
|
|
418
440
|
: {}),
|
|
@@ -721,6 +743,9 @@ export class ClaudeCliClient {
|
|
|
721
743
|
state.activeTurn = turn;
|
|
722
744
|
try {
|
|
723
745
|
state.cwd = this.resolveSessionCwd(turn.preparedTurn.promptContext);
|
|
746
|
+
state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
|
|
747
|
+
? 'deny-all'
|
|
748
|
+
: 'default';
|
|
724
749
|
const sessionPromptAppend = resolveTurnSessionPromptAppend(turn.preparedTurn);
|
|
725
750
|
state.visibilityKey = resolveSessionVisibilityKey(turn.preparedTurn.promptContext, sessionPromptAppend, turn.preparedTurn.projectionStrategy);
|
|
726
751
|
await this.ensureStarted();
|
|
@@ -999,13 +1024,15 @@ export class ClaudeCliClient {
|
|
|
999
1024
|
this.shutdownPromise = this.shutdownBackend(error);
|
|
1000
1025
|
}
|
|
1001
1026
|
handlePermissionRequest(params) {
|
|
1027
|
+
const state = this.findChannelStateBySessionId(params.sessionId);
|
|
1002
1028
|
return resolveCopilotPermissionResponse({
|
|
1003
1029
|
gateEnabled: true,
|
|
1004
1030
|
policyMode: 'enforce',
|
|
1005
1031
|
params,
|
|
1006
1032
|
logger: this.logger,
|
|
1007
1033
|
agentId: this.resolveSessionStoreAgentId()?.trim() || undefined,
|
|
1008
|
-
channelId:
|
|
1034
|
+
channelId: state?.channelId,
|
|
1035
|
+
toolPermissionMode: state?.toolPermissionMode ?? 'default',
|
|
1009
1036
|
});
|
|
1010
1037
|
}
|
|
1011
1038
|
invalidateSession(channelId, state, session) {
|
|
@@ -1057,10 +1084,10 @@ export class ClaudeCliClient {
|
|
|
1057
1084
|
}
|
|
1058
1085
|
state.queue.length = 0;
|
|
1059
1086
|
}
|
|
1060
|
-
|
|
1087
|
+
findChannelStateBySessionId(sessionId) {
|
|
1061
1088
|
for (const state of this.channels.values()) {
|
|
1062
1089
|
if (state.session?.sessionId === sessionId) {
|
|
1063
|
-
return state
|
|
1090
|
+
return state;
|
|
1064
1091
|
}
|
|
1065
1092
|
}
|
|
1066
1093
|
return undefined;
|
|
@@ -108,6 +108,8 @@ export declare class CodexCliClient {
|
|
|
108
108
|
private failAll;
|
|
109
109
|
private closeSession;
|
|
110
110
|
private clearIdleTimer;
|
|
111
|
+
private handlePermissionRequest;
|
|
112
|
+
private findChannelStateBySessionId;
|
|
111
113
|
private reconcileIdleChannelState;
|
|
112
114
|
private ensureSessionStoreLoaded;
|
|
113
115
|
private readPersistedSessionId;
|
|
@@ -8,6 +8,8 @@ import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js
|
|
|
8
8
|
import { AcpProgressCollector } from '../acp-progress-collector.js';
|
|
9
9
|
import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
|
|
10
10
|
import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
|
|
11
|
+
import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
|
|
12
|
+
import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
|
|
11
13
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
12
14
|
const SESSION_TAINTED_ERRORS = new WeakSet();
|
|
13
15
|
const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
|
|
@@ -193,26 +195,6 @@ function markSessionTainted(error) {
|
|
|
193
195
|
function isSessionTainted(error) {
|
|
194
196
|
return error instanceof Error && SESSION_TAINTED_ERRORS.has(error);
|
|
195
197
|
}
|
|
196
|
-
function createPermissionResponse(params) {
|
|
197
|
-
const options = Array.isArray(params.options) ? params.options : [];
|
|
198
|
-
const preferredKinds = ['allow_once', 'allow_always', 'reject_once', 'reject_always'];
|
|
199
|
-
for (const kind of preferredKinds) {
|
|
200
|
-
const match = options.find((option) => option.kind === kind);
|
|
201
|
-
if (match) {
|
|
202
|
-
return {
|
|
203
|
-
outcome: {
|
|
204
|
-
outcome: 'selected',
|
|
205
|
-
optionId: match.optionId,
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
outcome: {
|
|
212
|
-
outcome: 'cancelled',
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
198
|
function resolveBundledCodexAdapterPath() {
|
|
217
199
|
return require.resolve('@agentclientprotocol/codex-acp');
|
|
218
200
|
}
|
|
@@ -423,7 +405,7 @@ export class CodexCliClient {
|
|
|
423
405
|
const stream = this.runtime.ndJsonStream(output, input);
|
|
424
406
|
const app = this.runtime
|
|
425
407
|
.client({ name: 'borgee-agents-host' })
|
|
426
|
-
.onRequest(this.runtime.methods.client.session.requestPermission, ({ params }) => (
|
|
408
|
+
.onRequest(this.runtime.methods.client.session.requestPermission, ({ params }) => (this.handlePermissionRequest(params)));
|
|
427
409
|
const connection = app.connect(stream);
|
|
428
410
|
this.connection = connection;
|
|
429
411
|
void connection.closed.then(() => {
|
|
@@ -487,6 +469,9 @@ export class CodexCliClient {
|
|
|
487
469
|
state.activeTurn = turn;
|
|
488
470
|
try {
|
|
489
471
|
state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
|
|
472
|
+
state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
|
|
473
|
+
? 'deny-all'
|
|
474
|
+
: 'default';
|
|
490
475
|
const projectedPromptContext = await this.refreshProjectedPromptContextBestEffort(turn.channelId, turn.preparedTurn.promptContext);
|
|
491
476
|
state.additionalDirectories = this.resolveSessionAdditionalDirectories(projectedPromptContext);
|
|
492
477
|
state.visibilityKey = this.resolveSessionVisibilityKey(projectedPromptContext);
|
|
@@ -953,6 +938,25 @@ export class CodexCliClient {
|
|
|
953
938
|
clearTimeout(state.idleTimer);
|
|
954
939
|
state.idleTimer = undefined;
|
|
955
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
|
+
}
|
|
956
960
|
reconcileIdleChannelState(channelId, state) {
|
|
957
961
|
if (this.channels.get(channelId) !== state) {
|
|
958
962
|
return;
|
|
@@ -103,7 +103,7 @@ export declare class CopilotCliClient {
|
|
|
103
103
|
private closeSession;
|
|
104
104
|
private rejectQueuedTurnsAfterSessionTaint;
|
|
105
105
|
private clearIdleTimer;
|
|
106
|
-
private
|
|
106
|
+
private findChannelStateBySessionId;
|
|
107
107
|
private reconcileIdleChannelState;
|
|
108
108
|
private ensureSessionStoreLoaded;
|
|
109
109
|
private readPersistedSessionId;
|
|
@@ -4,6 +4,7 @@ import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientpr
|
|
|
4
4
|
import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
|
|
5
5
|
import { AcpProgressCollector } from '../acp-progress-collector.js';
|
|
6
6
|
import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
|
|
7
|
+
import { isDiscussionOnlyResolvedWorkspace } from '../../context/resolved-workspace.js';
|
|
7
8
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
8
9
|
const SESSION_TAINTED_ERRORS = new WeakSet();
|
|
9
10
|
const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
|
|
@@ -407,6 +408,9 @@ export class CopilotCliClient {
|
|
|
407
408
|
state.activeTurn = turn;
|
|
408
409
|
try {
|
|
409
410
|
state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
|
|
411
|
+
state.toolPermissionMode = isDiscussionOnlyResolvedWorkspace(turn.preparedTurn.promptContext)
|
|
412
|
+
? 'deny-all'
|
|
413
|
+
: 'default';
|
|
410
414
|
await this.ensureStarted();
|
|
411
415
|
await this.recycleSessionIfCwdChanged(channelId, state);
|
|
412
416
|
const session = await this.getOrCreateSession(channelId, state);
|
|
@@ -666,6 +670,7 @@ export class CopilotCliClient {
|
|
|
666
670
|
return Promise.race([promise, this.fatalPromise]);
|
|
667
671
|
}
|
|
668
672
|
handlePermissionRequest(params) {
|
|
673
|
+
const state = this.findChannelStateBySessionId(params.sessionId);
|
|
669
674
|
return resolveCopilotPermissionResponse({
|
|
670
675
|
gateEnabled: this.permissionPolicy.gateEnabled ?? false,
|
|
671
676
|
policyMode: this.permissionPolicy.policyMode ?? 'audit-only',
|
|
@@ -673,7 +678,8 @@ export class CopilotCliClient {
|
|
|
673
678
|
logger: this.logger,
|
|
674
679
|
auditSink: this.permissionPolicy.auditSink,
|
|
675
680
|
agentId: this.resolveSessionStoreAgentId()?.trim() || undefined,
|
|
676
|
-
channelId:
|
|
681
|
+
channelId: state?.channelId,
|
|
682
|
+
toolPermissionMode: state?.toolPermissionMode ?? 'default',
|
|
677
683
|
});
|
|
678
684
|
}
|
|
679
685
|
failAll(error) {
|
|
@@ -761,10 +767,10 @@ export class CopilotCliClient {
|
|
|
761
767
|
clearTimeout(state.idleTimer);
|
|
762
768
|
state.idleTimer = undefined;
|
|
763
769
|
}
|
|
764
|
-
|
|
770
|
+
findChannelStateBySessionId(sessionId) {
|
|
765
771
|
for (const state of this.channels.values()) {
|
|
766
772
|
if (state.session?.sessionId === sessionId) {
|
|
767
|
-
return state
|
|
773
|
+
return state;
|
|
768
774
|
}
|
|
769
775
|
}
|
|
770
776
|
return undefined;
|
|
@@ -8,7 +8,7 @@ interface CreateProviderOptions {
|
|
|
8
8
|
compatibilityGates?: ReadonlySet<string>;
|
|
9
9
|
localhostGateway?: LocalhostGatewayContextPublisher;
|
|
10
10
|
authorizationAuditSink?: AuthorizationAuditSinkLike;
|
|
11
|
-
controlPlane?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks'>;
|
|
11
|
+
controlPlane?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks' | 'listUsers'>;
|
|
12
12
|
}
|
|
13
13
|
export declare function createProvider(config: ProviderRuntimeConfig, debugLogger?: DebugLogger, options?: CreateProviderOptions): ProviderAdapter;
|
|
14
14
|
export {};
|
|
@@ -135,6 +135,8 @@ export function createProvider(config, debugLogger, options = {}) {
|
|
|
135
135
|
? options.localhostGateway
|
|
136
136
|
: undefined,
|
|
137
137
|
taskReader: options.controlPlane,
|
|
138
|
+
allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
|
|
139
|
+
resolveStableAgentId: config.resolveStableAgentId,
|
|
138
140
|
})
|
|
139
141
|
: undefined;
|
|
140
142
|
const resolveInternalProjection = () => resolveInternalProjectionStrategy();
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface ProviderCommandConfig {
|
|
|
10
10
|
copilotSessionTtlMinutes: number;
|
|
11
11
|
/** Minutes a provider's shared ACP adapter process may stay idle before it is shut down; `0` keeps it resident. */
|
|
12
12
|
providerIdleShutdownMinutes: number;
|
|
13
|
+
/**
|
|
14
|
+
* Whether this agents-host may let a hosted agent enter an independent
|
|
15
|
+
* execution.local_directory workspace for a task thread created by another
|
|
16
|
+
* agent. Undefined keeps the default allow behavior.
|
|
17
|
+
*/
|
|
18
|
+
allowCrossAgentIndependentWorkspaceHandoff?: boolean;
|
|
13
19
|
}
|
|
14
20
|
export interface ProviderRuntimeConfig extends ProviderCommandConfig {
|
|
15
21
|
provider: ProviderKind;
|
|
@@ -286,6 +292,8 @@ export interface ProviderInput {
|
|
|
286
292
|
taskThreadCollaborationContract?: TaskThreadCollaborationContract;
|
|
287
293
|
collaborationCapabilities?: CollaborationCapabilityDeclaration;
|
|
288
294
|
missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
|
|
295
|
+
taskAssignmentContextOverride?: TaskAssignmentThreadContext;
|
|
296
|
+
taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
|
|
289
297
|
}
|
|
290
298
|
export interface ProviderProtocolContext {
|
|
291
299
|
anchorMessageId: string;
|
|
@@ -334,12 +342,17 @@ export interface ChannelResolvedWorkspaceContext {
|
|
|
334
342
|
owningChannelId: string;
|
|
335
343
|
rootPath: string;
|
|
336
344
|
}
|
|
337
|
-
export interface
|
|
338
|
-
authority: 'task';
|
|
345
|
+
export interface TaskThreadScratchWorkspaceContext {
|
|
346
|
+
authority: 'task-thread-scratch';
|
|
347
|
+
rootPath: string;
|
|
348
|
+
reason?: 'missing-or-invalid-execution-target' | 'cross-agent-independent-workspace-disabled';
|
|
349
|
+
}
|
|
350
|
+
export interface TaskExecutionTargetResolvedWorkspaceContext {
|
|
351
|
+
authority: 'task-execution-target';
|
|
339
352
|
taskId: string;
|
|
340
353
|
rootPath: string;
|
|
341
354
|
}
|
|
342
|
-
export type ResolvedWorkspaceContext = ChannelResolvedWorkspaceContext |
|
|
355
|
+
export type ResolvedWorkspaceContext = ChannelResolvedWorkspaceContext | TaskThreadScratchWorkspaceContext | TaskExecutionTargetResolvedWorkspaceContext;
|
|
343
356
|
export interface RuntimeSurface {
|
|
344
357
|
schemaVersion: 1;
|
|
345
358
|
turnMode: ProviderCollaborationTurnMode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgee/agents-host",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.62",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"tsx": "^4.20.5",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
37
|
"vitest": "^4.1.5",
|
|
38
|
-
"@borgee/plugin-sdk": "0.
|
|
38
|
+
"@borgee/plugin-sdk": "0.12.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"predev": "pnpm --filter @borgee/plugin-sdk build",
|
|
@@ -64,15 +64,15 @@ Everything below writes that leading interpreter and script path as `borgee-agen
|
|
|
64
64
|
|
|
65
65
|
On `task get`, `task update`, `task set-property` and `task delete-property` the task id is an optional leading positional. Inside a task thread, omitting it addresses this thread's own task: the host's published binding names it, and where the host published none the gateway scans the parent channel's visible tasks for this thread instead — so pass the id explicitly when that scan cannot land on a single task. `task list` and `task create` are refused inside a task thread; they belong to the parent channel. In a parent channel every one of those ids is required, and omitting it answers `not_found`.
|
|
66
66
|
|
|
67
|
-
The keys `task set-property` and `task delete-property` accept are a closed set, and a value has a size limit: see `references/task-properties.md`. In this slice, `workspace.mode`
|
|
67
|
+
The keys `task set-property` and `task delete-property` accept are a closed set, and a value has a size limit: see `references/task-properties.md`. In this slice, `workspace.mode` still records the host-managed scratch isolation hint, while `execution.local_directory` is a human-owned execution target that hosted agents may read but must not retarget through the generic task-property rail.
|
|
68
68
|
|
|
69
69
|
## What is available on a turn
|
|
70
70
|
|
|
71
|
-
`health`, `bootstrap`, `whoami`, `history` and the task commands are live on every turn whose prompt names a gateway credential file.
|
|
71
|
+
`health`, `bootstrap`, `whoami`, `history`, `users` and the task commands are live on every turn whose prompt names a gateway credential file.
|
|
72
72
|
|
|
73
|
-
`
|
|
73
|
+
`draft`, `send` and `mention` are live only where collaboration is enabled; the prompt says when they are not, and the gateway answers `not_found` for all three. They additionally need the turn execution id the prompt carries: no command returns that id and the gateway credential file does not hold it.
|
|
74
74
|
|
|
75
|
-
A `not_found` from `draft` therefore has two readings — collaboration is off, or the host holds no draft for this turn yet — so take it as an answer about the draft, not as evidence that `users`, `send` and `mention` have gone.
|
|
75
|
+
A `not_found` from `draft` therefore has two readings — collaboration is off, or the host holds no draft for this turn yet — so take it as an answer about the draft, not as evidence that `users`, `send` and `mention` have all gone.
|
|
76
76
|
|
|
77
77
|
Whether you are in a parent channel or inside a task assignment thread is in the prompt too, and it decides which task grammar above applies.
|
|
78
78
|
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
| 429 `collaboration_target_cooldown` | The same reply target or mention set was addressed moments ago. | Do not repeat it. |
|
|
23
23
|
| 404 `not_found` on `task get` / `task update` / a property command with no task id | You are not inside a task thread, so there is no current task to resolve. | Pass the task id. |
|
|
24
24
|
| 404 `not_found` on a task command with a task id | The task does not exist, or belongs to another channel. | Check the id with `task list`. |
|
|
25
|
-
| 404 `not_found` on `
|
|
26
|
-
| 404 `not_found` on `draft` | Either collaboration is not enabled for this turn, or the host holds no draft for it yet — a draft exists only once the turn has produced visible reply text. | Not a verdict on the other collaboration commands: `users
|
|
25
|
+
| 404 `not_found` on `send`, `mention` | Collaboration is not enabled for this turn, so those commands do not exist. | Do not use them; the turn prompt says when they are live. |
|
|
26
|
+
| 404 `not_found` on `draft` | Either collaboration is not enabled for this turn, or the host holds no draft for it yet — a draft exists only once the turn has produced visible reply text. | Not a verdict on the other collaboration commands: `users` may still answer on this same turn, and `send` / `mention` may answer once collaboration is live. Carry on and read the draft later if you still need it. |
|
|
27
27
|
| 404 `bootstrap_unavailable` | The host has not published this turn's channel payload yet. | Retry the read once; if it persists, continue without it. |
|
|
28
28
|
| 400 `task_thread_collection_not_allowed` | `task list` or `task create` inside a task thread. | Those belong to the parent channel. |
|
|
29
29
|
| 400 `multiline_message_body_not_allowed`, `message_body_too_verbose` | An auxiliary send must be one line of at most 12 words, and the `<@id>` the CLI appends counts as one of them — each extra `--mention` costs another. | Shorten it to a single-line notice. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Task properties
|
|
2
2
|
|
|
3
|
-
A task property associates a task with something that lives outside it, or with a closed task-level runtime choice. Use one to record what a reader would otherwise have to hunt for in the thread — the pull request that implements the task, the issue it came from, or the
|
|
3
|
+
A task property associates a task with something that lives outside it, or with a closed task-level runtime choice. Use one to record what a reader would otherwise have to hunt for in the thread — the pull request that implements the task, the issue it came from, or the execution metadata a human attached to it.
|
|
4
4
|
|
|
5
5
|
Read them back with `task get`: every task response carries a `properties` object, `{}` when the task has none.
|
|
6
6
|
|
|
@@ -21,7 +21,8 @@ The key set is closed; writing an unregistered key is rejected with `unknown_pro
|
|
|
21
21
|
| --- | --- |
|
|
22
22
|
| `link.pr` | The pull request that implements this task. Set it as soon as the PR exists, not at the end. |
|
|
23
23
|
| `link.issue` | The issue or ticket the task originates from. |
|
|
24
|
-
| `
|
|
24
|
+
| `execution.local_directory` | The explicit local directory a human selected as this task thread's execution target. New tasks default it from the immediately previous task thread in the same channel: if that previous task carried a value, the new task inherits it; if the previous task left it unset, the new task also starts unset. Hosted agents can read it, but the generic agent task-property rail must not write it. Humans set it through the existing user rail. The extra single-human-user guard applies when a task thread is about to switch into somebody's own local workspace/project directory, not as a blanket rule on every property write. A separate agents-host local policy switch governs whether a hosted agent may enter an independent workspace for a task thread that originated from another agent's task. |
|
|
25
|
+
| `workspace.mode` | The host-managed scratch-workspace isolation hint for task threads. `inherit` keeps the scratch workspace on the shared host-managed path, while `isolated` reserves the task's own host-managed scratch workspace shape. Deleting the key returns to the default `inherit` behavior. |
|
|
25
26
|
| `agent.session_id` | Do not write this. It is registered, so a write is accepted and lands — overwriting the host's record of which provider session worked this task. The host writes it itself after each turn. |
|
|
26
27
|
|
|
27
28
|
## One key per call
|
|
@@ -30,4 +31,4 @@ Each call writes exactly one key, and that is what makes it safe to write a prop
|
|
|
30
31
|
|
|
31
32
|
## Value
|
|
32
33
|
|
|
33
|
-
A value is a plain string of at most 8 KiB; a longer one is rejected with `property_value_too_long`. It is a reference — a URL, an identifier — never a document. Some registered keys are closed enums: `workspace.mode` accepts only `inherit` or `isolated`.
|
|
34
|
+
A value is a plain string of at most 8 KiB; a longer one is rejected with `property_value_too_long`. It is a reference — a URL, an identifier — never a document. `execution.local_directory` must be a non-empty absolute path string. Before a human lets a task thread switch into their own local workspace/project directory, first verify that the task's parent channel has exactly one human user; otherwise keep the thread discussion-only. If a hosted agent is trying to enter an independent workspace for a task thread that originated from another agent's task, this agents-host's local handoff policy must also allow it. Some registered keys are closed enums: `workspace.mode` accepts only `inherit` or `isolated`.
|