@borgee/agents-host 0.2.73 → 0.2.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -30
- package/dist/agents-host-supervisor.d.ts +1 -0
- package/dist/agents-host-supervisor.js +3 -1
- package/dist/agents-host.d.ts +6 -4
- package/dist/agents-host.js +117 -16
- package/dist/background-runs.d.ts +23 -0
- package/dist/background-runs.js +153 -0
- package/dist/chat/chat-control-plane.d.ts +6 -1
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -2
- package/dist/chat/sdk-chat-control-plane.js +7 -0
- package/dist/context/claude-file-brief.js +3 -3
- package/dist/context/injection.d.ts +18 -12
- package/dist/context/injection.js +53 -53
- package/dist/context/prompt.js +10 -10
- package/dist/context/resolved-working-folder.d.ts +3 -0
- package/dist/context/resolved-working-folder.js +106 -0
- package/dist/context/turn-preparation.js +1 -1
- package/dist/local-config.d.ts +7 -0
- package/dist/local-config.js +158 -12
- package/dist/managed-daemon.js +42 -35
- package/dist/plugin-sdk.js +179 -9
- package/dist/plugin-sdk.js.map +3 -3
- package/dist/policy/authorization-audit.d.ts +1 -1
- package/dist/policy/copilot-permission.d.ts +9 -0
- package/dist/policy/copilot-permission.js +120 -1
- package/dist/progress-to-activity.d.ts +1 -1
- package/dist/progress-to-activity.js +1 -0
- package/dist/providers/claude/adapter.d.ts +1 -1
- package/dist/providers/claude/adapter.js +7 -0
- package/dist/providers/claude/cli-client.js +4 -3
- package/dist/providers/codex/adapter.d.ts +1 -1
- package/dist/providers/codex/adapter.js +7 -0
- package/dist/providers/codex/cli-client.js +8 -7
- package/dist/providers/codex/project-doc.js +6 -6
- package/dist/providers/copilot/adapter.d.ts +6 -2
- package/dist/providers/copilot/adapter.js +27 -1
- package/dist/providers/copilot/cli-client.d.ts +37 -10
- package/dist/providers/copilot/cli-client.js +751 -122
- package/dist/providers/copilot/sdk-session.d.ts +149 -0
- package/dist/providers/copilot/sdk-session.js +981 -0
- package/dist/providers/create-provider.js +33 -0
- package/dist/providers/provider-adapter.d.ts +16 -1
- package/dist/providers/provider-adapter.js +13 -0
- package/dist/types.d.ts +32 -11
- package/package.json +3 -2
- package/skills/borgee-agent/references/task-properties.md +3 -3
- package/skills/borgee-agent/scripts/borgee-agent.mjs +1 -1
- package/skills/borgee-agent/scripts/borgee-agent.py +1 -1
- package/dist/context/resolved-workspace.d.ts +0 -3
- package/dist/context/resolved-workspace.js +0 -106
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const STOP_UNSUPPORTED_REASON = 'This provider does not support targeted background-run cancellation';
|
|
3
|
+
const MAX_TRACKED_RUNS = 128;
|
|
4
|
+
export class BackgroundRunTracker {
|
|
5
|
+
report;
|
|
6
|
+
options;
|
|
7
|
+
now;
|
|
8
|
+
mintRunId;
|
|
9
|
+
runs = new Map();
|
|
10
|
+
runsById = new Map();
|
|
11
|
+
constructor(report, options, now = Date.now, mintRunId = stableRunId) {
|
|
12
|
+
this.report = report;
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.now = now;
|
|
15
|
+
this.mintRunId = mintRunId;
|
|
16
|
+
}
|
|
17
|
+
observe(channelId, update) {
|
|
18
|
+
if (!this.options.lifecycleSupported || update.type !== 'background_run')
|
|
19
|
+
return;
|
|
20
|
+
const key = `${channelId}\0${update.run.providerRunId}`;
|
|
21
|
+
const held = this.runs.get(key);
|
|
22
|
+
const state = runState(update.run.state);
|
|
23
|
+
const stopSupported = update.run.stopSupported &&
|
|
24
|
+
this.options.targetedCancellationSupported &&
|
|
25
|
+
this.options.cancel !== undefined;
|
|
26
|
+
const executionChanged = held !== undefined &&
|
|
27
|
+
update.run.startedAt !== undefined &&
|
|
28
|
+
update.run.startedAt !== held.startedAt;
|
|
29
|
+
const startsNewExecution = !held || executionChanged || (isTerminal(held.state) && !isTerminal(state));
|
|
30
|
+
if (held && isTerminal(held.state) && isTerminal(state) && !startsNewExecution)
|
|
31
|
+
return;
|
|
32
|
+
const startedAt = startsNewExecution
|
|
33
|
+
? (update.run.startedAt ?? this.now())
|
|
34
|
+
: (update.run.startedAt ?? held.startedAt);
|
|
35
|
+
const run = startsNewExecution
|
|
36
|
+
? {
|
|
37
|
+
channelId,
|
|
38
|
+
providerRunId: update.run.providerRunId,
|
|
39
|
+
runId: this.mintRunId(channelId, update.run.providerRunId, startedAt),
|
|
40
|
+
label: update.run.label,
|
|
41
|
+
state,
|
|
42
|
+
startedAt,
|
|
43
|
+
finishedAt: update.run.finishedAt,
|
|
44
|
+
reason: update.run.reason,
|
|
45
|
+
stopSupported,
|
|
46
|
+
}
|
|
47
|
+
: {
|
|
48
|
+
...held,
|
|
49
|
+
label: update.run.label,
|
|
50
|
+
state,
|
|
51
|
+
startedAt,
|
|
52
|
+
finishedAt: update.run.finishedAt,
|
|
53
|
+
reason: update.run.reason,
|
|
54
|
+
stopSupported,
|
|
55
|
+
};
|
|
56
|
+
if (isTerminal(state) && run.finishedAt === undefined) {
|
|
57
|
+
run.finishedAt = this.now();
|
|
58
|
+
}
|
|
59
|
+
if (held && held.runId !== run.runId) {
|
|
60
|
+
this.runsById.delete(held.runId);
|
|
61
|
+
}
|
|
62
|
+
this.runs.delete(key);
|
|
63
|
+
this.runs.set(key, run);
|
|
64
|
+
this.runsById.set(run.runId, run);
|
|
65
|
+
while (this.runs.size > MAX_TRACKED_RUNS) {
|
|
66
|
+
const oldestKey = this.runs.keys().next().value;
|
|
67
|
+
if (typeof oldestKey !== 'string') {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const oldest = this.runs.get(oldestKey);
|
|
71
|
+
this.runs.delete(oldestKey);
|
|
72
|
+
if (oldest) {
|
|
73
|
+
this.runsById.delete(oldest.runId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.publish(run);
|
|
77
|
+
}
|
|
78
|
+
async stop(channelId, runId) {
|
|
79
|
+
if (!this.options.lifecycleSupported ||
|
|
80
|
+
!this.options.targetedCancellationSupported ||
|
|
81
|
+
!this.options.cancel) {
|
|
82
|
+
return {
|
|
83
|
+
outcome: 'unsupported',
|
|
84
|
+
reason: STOP_UNSUPPORTED_REASON,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const run = this.runsById.get(runId);
|
|
88
|
+
if (!run || run.channelId !== channelId || isTerminal(run.state)) {
|
|
89
|
+
return { outcome: 'not_found' };
|
|
90
|
+
}
|
|
91
|
+
if (!run.stopSupported) {
|
|
92
|
+
return {
|
|
93
|
+
outcome: 'unsupported',
|
|
94
|
+
reason: STOP_UNSUPPORTED_REASON,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (!(await this.options.cancel(channelId, run.providerRunId))) {
|
|
98
|
+
return { outcome: 'not_found' };
|
|
99
|
+
}
|
|
100
|
+
const current = this.runsById.get(runId);
|
|
101
|
+
if (current && current.channelId === channelId && !isTerminal(current.state)) {
|
|
102
|
+
current.state = 'stopping';
|
|
103
|
+
this.publish(current);
|
|
104
|
+
}
|
|
105
|
+
return { outcome: 'stopped' };
|
|
106
|
+
}
|
|
107
|
+
publish(run) {
|
|
108
|
+
this.report(run.channelId, {
|
|
109
|
+
shape: 'background_run',
|
|
110
|
+
runId: run.runId,
|
|
111
|
+
label: run.label,
|
|
112
|
+
state: run.state,
|
|
113
|
+
startedAt: run.startedAt,
|
|
114
|
+
...(run.finishedAt !== undefined ? { finishedAt: run.finishedAt } : {}),
|
|
115
|
+
...(run.reason ? { reason: run.reason } : {}),
|
|
116
|
+
stopSupported: run.stopSupported,
|
|
117
|
+
...(!run.stopSupported ? { stopUnsupportedReason: STOP_UNSUPPORTED_REASON } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function runState(status) {
|
|
122
|
+
switch (status) {
|
|
123
|
+
case 'running':
|
|
124
|
+
return 'running';
|
|
125
|
+
case 'waiting':
|
|
126
|
+
return 'waiting';
|
|
127
|
+
case 'completed':
|
|
128
|
+
return 'completed';
|
|
129
|
+
case 'failed':
|
|
130
|
+
return 'failed';
|
|
131
|
+
case 'cancelled':
|
|
132
|
+
return 'cancelled';
|
|
133
|
+
case 'unknown':
|
|
134
|
+
return 'unknown';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function isTerminal(state) {
|
|
138
|
+
return (state === 'completed' ||
|
|
139
|
+
state === 'failed' ||
|
|
140
|
+
state === 'cancelled' ||
|
|
141
|
+
state === 'unknown');
|
|
142
|
+
}
|
|
143
|
+
function stableRunId(channelId, providerRunId, startedAt) {
|
|
144
|
+
const digest = createHash('sha256')
|
|
145
|
+
.update(channelId)
|
|
146
|
+
.update('\0')
|
|
147
|
+
.update(providerRunId)
|
|
148
|
+
.update('\0')
|
|
149
|
+
.update(String(startedAt))
|
|
150
|
+
.digest('hex')
|
|
151
|
+
.slice(0, 32);
|
|
152
|
+
return `run_${digest}`;
|
|
153
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ReportTurnActivityInput, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
|
|
1
|
+
import type { AgentActivity, ReportTurnActivityInput, StopBackgroundRunHandler, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
|
|
2
2
|
import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
|
|
3
3
|
export interface ChatControlPlane {
|
|
4
4
|
onStopTurn?(handler: StopTurnHandler | undefined): void;
|
|
5
|
+
onStopBackgroundRun?(handler: StopBackgroundRunHandler | undefined): void;
|
|
5
6
|
connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
|
|
6
7
|
close(): Promise<void>;
|
|
7
8
|
postMessage(input: PostMessageInput): Promise<PostedMessage>;
|
|
@@ -10,6 +11,10 @@ export interface ChatControlPlane {
|
|
|
10
11
|
startTyping(channelId: string): () => void;
|
|
11
12
|
/** Opens one turn's report on the activity rail. Fire-and-forget: nothing recovers what it sends. */
|
|
12
13
|
reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
|
|
14
|
+
reportActivity?(input: {
|
|
15
|
+
channelId: string;
|
|
16
|
+
activity: AgentActivity;
|
|
17
|
+
}): void;
|
|
13
18
|
getMe(): Promise<MeResponseUser>;
|
|
14
19
|
listUsers(): Promise<DirectoryUser[]>;
|
|
15
20
|
listChannels(): Promise<ChannelSummary[]>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type StopTurnHandler, type TurnActivityReporter } from '../plugin-sdk.js';
|
|
1
|
+
import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type StopTurnHandler, type StopBackgroundRunHandler, type TurnActivityReporter } from '../plugin-sdk.js';
|
|
2
2
|
import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
|
|
3
3
|
import type { ChatControlPlane } from './chat-control-plane.js';
|
|
4
|
-
type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'onStopTurn' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
|
|
4
|
+
type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'onStopTurn' | 'onStopBackgroundRun' | 'reportActivity' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
|
|
5
5
|
type PluginClientFactory = (options: BorgeePluginOptions) => PluginClientLike;
|
|
6
6
|
type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'>;
|
|
7
7
|
/**
|
|
@@ -17,9 +17,11 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
|
|
|
17
17
|
private me;
|
|
18
18
|
constructor(baseUrl: string, apiKey: string, createClient?: PluginClientFactory, options?: SdkChatControlPlaneOptions);
|
|
19
19
|
onStopTurn(handler: StopTurnHandler | undefined): void;
|
|
20
|
+
onStopBackgroundRun(handler: StopBackgroundRunHandler | undefined): void;
|
|
20
21
|
connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
|
|
21
22
|
close(): Promise<void>;
|
|
22
23
|
postMessage(input: PostMessageInput): Promise<PostedMessage>;
|
|
24
|
+
reportActivity(input: Parameters<BorgeePluginClient['reportActivity']>[0]): void;
|
|
23
25
|
editMessage(messageId: string, content: string): Promise<void>;
|
|
24
26
|
deleteMessage(messageId: string): Promise<void>;
|
|
25
27
|
startTyping(channelId: string): () => void;
|
|
@@ -17,6 +17,9 @@ export class SdkChatControlPlane {
|
|
|
17
17
|
onStopTurn(handler) {
|
|
18
18
|
this.client.onStopTurn(handler);
|
|
19
19
|
}
|
|
20
|
+
onStopBackgroundRun(handler) {
|
|
21
|
+
this.client.onStopBackgroundRun?.(handler);
|
|
22
|
+
}
|
|
20
23
|
async connect(onMessage) {
|
|
21
24
|
this.unsubscribe = this.client.on('message', (event) => {
|
|
22
25
|
const message = mapInboundToChannelMessage(event);
|
|
@@ -50,6 +53,7 @@ export class SdkChatControlPlane {
|
|
|
50
53
|
this.unsubscribe?.();
|
|
51
54
|
this.unsubscribe = null;
|
|
52
55
|
this.client.onStopTurn(undefined);
|
|
56
|
+
this.client.onStopBackgroundRun?.(undefined);
|
|
53
57
|
await this.client.close();
|
|
54
58
|
}
|
|
55
59
|
async postMessage(input) {
|
|
@@ -60,6 +64,9 @@ export class SdkChatControlPlane {
|
|
|
60
64
|
});
|
|
61
65
|
return { messageId: sent.messageId };
|
|
62
66
|
}
|
|
67
|
+
reportActivity(input) {
|
|
68
|
+
this.client.reportActivity(input);
|
|
69
|
+
}
|
|
63
70
|
async editMessage(messageId, content) {
|
|
64
71
|
await this.client.editMessage({ messageId, body: content });
|
|
65
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildCollaborationCapabilityDeclarationSummaryLines, } from './collaboration-capabilities-diagnostics.js';
|
|
2
2
|
import { MAIN_SESSION_DELEGATION_LINES } from './main-session-delegation.js';
|
|
3
|
-
import {
|
|
3
|
+
import { buildResolvedWorkingFolderGuidanceLines } from './resolved-working-folder.js';
|
|
4
4
|
import { buildSkillManualLines, buildSkillManualReadLine } from './skill-manual.js';
|
|
5
5
|
import { PARENT_CHANNEL_TASK_BOUNDARY_LINES } from './task-channel-boundary.js';
|
|
6
6
|
function buildStableSkillLines(context) {
|
|
@@ -60,8 +60,8 @@ export function buildClaudeFileBrief(context) {
|
|
|
60
60
|
'',
|
|
61
61
|
...sessionDelegationLines,
|
|
62
62
|
...(() => {
|
|
63
|
-
const
|
|
64
|
-
return
|
|
63
|
+
const workingFolderLines = buildResolvedWorkingFolderGuidanceLines(context);
|
|
64
|
+
return workingFolderLines.length > 0 ? ['', ...workingFolderLines] : [];
|
|
65
65
|
})(),
|
|
66
66
|
...(() => {
|
|
67
67
|
const capabilityLines = buildCollaborationCapabilityDeclarationSummaryLines(context?.collaborationCapabilities);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatControlPlane } from '../chat/chat-control-plane.js';
|
|
2
|
-
import type { AttentionSnapshot, ProjectionStrategy, CollaborationCapabilityDeclaration, CollaborationOutcomeSnapshot, CompactionSnapshot, LocalhostGatewayBootstrapMetadata, MissedCollaborationDiagnostic, ProviderKind, ProviderCollaborationContext,
|
|
2
|
+
import type { AttentionSnapshot, ProjectionStrategy, CollaborationCapabilityDeclaration, CollaborationOutcomeSnapshot, CompactionSnapshot, LocalhostGatewayBootstrapMetadata, MissedCollaborationDiagnostic, ProviderKind, ProviderCollaborationContext, ResolvedWorkingFolderContext, RuntimeSurface, SkillRuntimeBootstrapMetadata, TaskThreadCollaborationContract, TaskAssignmentThreadContext } from '../types.js';
|
|
3
3
|
export interface SkillRuntimeBootstrapPayload {
|
|
4
4
|
skillDirectoryPath: string;
|
|
5
5
|
nodeCliPath: string;
|
|
@@ -18,7 +18,13 @@ export interface ChannelContextPayload {
|
|
|
18
18
|
skillRuntime?: SkillRuntimeBootstrapPayload;
|
|
19
19
|
localhostGateway?: LocalhostGatewayBootstrapMetadata;
|
|
20
20
|
taskAssignmentContext?: TaskAssignmentThreadContext;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Serialized into `context.json` and echoed by the gateway bootstrap response, so the key name is
|
|
23
|
+
* observable outside this process: `cleanup --purge` reads payloads written by whichever version
|
|
24
|
+
* happened to run last, and a payload whose key it does not recognize is skipped, silently leaving
|
|
25
|
+
* the directory it names on disk.
|
|
26
|
+
*/
|
|
27
|
+
resolvedWorkspace?: ResolvedWorkingFolderContext;
|
|
22
28
|
}
|
|
23
29
|
export interface PreparedChannelContext {
|
|
24
30
|
directoryPath: string;
|
|
@@ -29,7 +35,7 @@ export interface PreparedChannelContext {
|
|
|
29
35
|
gatewayCredentialPath?: string;
|
|
30
36
|
skillRuntime?: SkillRuntimeBootstrapMetadata;
|
|
31
37
|
localhostGateway?: LocalhostGatewayBootstrapMetadata;
|
|
32
|
-
|
|
38
|
+
resolvedWorkingFolder?: ResolvedWorkingFolderContext;
|
|
33
39
|
}
|
|
34
40
|
export declare class ChannelContextPreparationError extends Error {
|
|
35
41
|
readonly partialContext: PreparedChannelContext;
|
|
@@ -109,7 +115,7 @@ interface FileChannelContextStoreOptions {
|
|
|
109
115
|
skillRuntimeEnabled?: boolean;
|
|
110
116
|
skillAssetResolver?: SkillAssetResolver;
|
|
111
117
|
localhostGateway?: LocalhostGatewayContextPublisher;
|
|
112
|
-
|
|
118
|
+
workingFolderCollectionRootDir?: string;
|
|
113
119
|
taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks' | 'listUsers'>;
|
|
114
120
|
taskResolutionTimeoutMs?: number;
|
|
115
121
|
allowCrossAgentIndependentWorkspaceHandoff?: boolean;
|
|
@@ -128,13 +134,13 @@ export declare function resolveChannelContextDirectory(stateRootDir: string, cha
|
|
|
128
134
|
export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
|
|
129
135
|
export declare function resolveTaskAssignmentStateDirectory(stateRootDir: string, channelId: string): string;
|
|
130
136
|
export declare function resolveTaskAssignmentStatePath(stateRootDir: string, channelId: string): string;
|
|
131
|
-
export declare function
|
|
132
|
-
export declare function
|
|
133
|
-
export declare function
|
|
134
|
-
export declare function
|
|
135
|
-
export declare function
|
|
136
|
-
export declare const
|
|
137
|
-
export declare function
|
|
137
|
+
export declare function resolveTaskWorkingFolderRootDirectory(channelRootDir: string): string;
|
|
138
|
+
export declare function resolveManagedWorkingFolderCollectionRoot(workingFolderCollectionRootDir: string | undefined, env?: NodeJS.ProcessEnv, resolvedHomeDir?: string): string;
|
|
139
|
+
export declare function resolveChannelWorkingFolderRootDirectory(workingFolderCollectionRootDir: string, channelId: string): string;
|
|
140
|
+
export declare function resolveChannelWorkingFolderDirectory(workingFolderCollectionRootDir: string, channelId: string): string;
|
|
141
|
+
export declare function resolveTaskIsolatedWorkingFolderDirectory(workingFolderCollectionRootDir: string, channelId: string, taskId: string): string;
|
|
142
|
+
export declare const resolveTaskWorkingFolderDirectory: typeof resolveTaskIsolatedWorkingFolderDirectory;
|
|
143
|
+
export declare function resolveTaskThreadScratchWorkingFolderDirectory(workingFolderCollectionRootDir: string, channelId: string): string;
|
|
138
144
|
export declare function resolveChannelGatewayCredentialPath(stateRootDir: string, channelId: string): string;
|
|
139
145
|
export declare function resolveGatewayCredentialPathFromPayloadPath(payloadPath: string): string;
|
|
140
146
|
export declare function resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath: string): string;
|
|
@@ -146,7 +152,7 @@ export declare class FileChannelContextStore implements ChannelContextStore {
|
|
|
146
152
|
private readonly skillRuntimeEnabled;
|
|
147
153
|
private readonly skillAssetResolver;
|
|
148
154
|
private readonly localhostGateway?;
|
|
149
|
-
private readonly
|
|
155
|
+
private readonly workingFolderCollectionRootDir;
|
|
150
156
|
private readonly taskReader?;
|
|
151
157
|
private readonly taskResolutionTimeoutMs;
|
|
152
158
|
private readonly allowCrossAgentIndependentWorkspaceHandoff;
|
|
@@ -12,12 +12,12 @@ const TASK_ASSIGNMENT_STATE_FILENAME = 'context.json';
|
|
|
12
12
|
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
13
13
|
const CLAUDE_PROJECTED_BRIEF_FILENAME = 'CLAUDE.md';
|
|
14
14
|
const DEFAULT_HOME_BORGEE_DIRNAME = '.borgee';
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
15
|
+
const CHANNEL_WORKING_FOLDER_COLLECTION_DIRNAME = 'channels';
|
|
16
|
+
const CHANNEL_WORKING_FOLDER_DIRNAME = 'workspace';
|
|
17
|
+
const TASK_ISOLATED_WORKING_FOLDER_COLLECTION_DIRNAME = 'tasks';
|
|
18
|
+
const TASK_THREAD_SCRATCH_WORKING_FOLDER_DIRNAME = 'scratch';
|
|
19
19
|
const TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY = 'execution.local_directory';
|
|
20
|
-
const
|
|
20
|
+
const DEFAULT_TASK_WORKING_FOLDER_RESOLUTION_TIMEOUT_MS = 1_500;
|
|
21
21
|
export class ChannelContextPreparationError extends Error {
|
|
22
22
|
partialContext;
|
|
23
23
|
constructor(message, partialContext, options) {
|
|
@@ -113,7 +113,7 @@ function buildPromptContextForClaudeProjectedBrief(preparedContext) {
|
|
|
113
113
|
skillRuntime: preparedContext.skillRuntime,
|
|
114
114
|
localhostGateway: preparedContext.localhostGateway,
|
|
115
115
|
taskAssignmentContext: preparedContext.payload.taskAssignmentContext,
|
|
116
|
-
|
|
116
|
+
resolvedWorkingFolder: preparedContext.resolvedWorkingFolder,
|
|
117
117
|
runtimeSurface: preparedContext.payload.runtimeSurface,
|
|
118
118
|
collaborationOutcome: preparedContext.payload.collaborationOutcome,
|
|
119
119
|
attentionSnapshot: preparedContext.payload.attentionSnapshot,
|
|
@@ -246,17 +246,17 @@ async function readExistingTaskAssignmentContext(statePath, fileSystem) {
|
|
|
246
246
|
return undefined;
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
-
function
|
|
249
|
+
function buildChannelResolvedWorkingFolder(workingFolderCollectionRootDir, channelId) {
|
|
250
250
|
return {
|
|
251
251
|
authority: 'channel',
|
|
252
252
|
owningChannelId: channelId,
|
|
253
|
-
rootPath:
|
|
253
|
+
rootPath: resolveChannelWorkingFolderDirectory(workingFolderCollectionRootDir, channelId),
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
|
-
function
|
|
256
|
+
function buildTaskThreadScratchResolvedWorkingFolder(workingFolderCollectionRootDir, channelId, reason = 'missing-or-invalid-execution-target') {
|
|
257
257
|
return {
|
|
258
258
|
authority: 'task-thread-scratch',
|
|
259
|
-
rootPath:
|
|
259
|
+
rootPath: resolveTaskThreadScratchWorkingFolderDirectory(workingFolderCollectionRootDir, channelId),
|
|
260
260
|
reason,
|
|
261
261
|
};
|
|
262
262
|
}
|
|
@@ -312,23 +312,23 @@ async function isExistingDirectory(fileSystem, path) {
|
|
|
312
312
|
return false;
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
|
-
async function
|
|
316
|
-
const
|
|
317
|
-
const
|
|
315
|
+
async function resolveTaskThreadWorkingFolderContext(params) {
|
|
316
|
+
const channelWorkingFolder = buildChannelResolvedWorkingFolder(params.workingFolderCollectionRootDir, params.channelId);
|
|
317
|
+
const discussionOnlyWorkingFolder = buildTaskThreadScratchResolvedWorkingFolder(params.workingFolderCollectionRootDir, params.channelId);
|
|
318
318
|
const taskAssignmentContext = params.taskAssignmentContext;
|
|
319
319
|
const currentTaskId = taskAssignmentContext?.currentTaskId?.trim();
|
|
320
320
|
if (taskAssignmentContext?.active !== true) {
|
|
321
|
-
return { taskAssignmentContext,
|
|
321
|
+
return { taskAssignmentContext, resolvedWorkingFolder: channelWorkingFolder };
|
|
322
322
|
}
|
|
323
323
|
if (!currentTaskId || !params.taskReader) {
|
|
324
|
-
return { taskAssignmentContext,
|
|
324
|
+
return { taskAssignmentContext, resolvedWorkingFolder: discussionOnlyWorkingFolder };
|
|
325
325
|
}
|
|
326
326
|
try {
|
|
327
|
-
const task = await withDeadline(findTaskForThread(params.taskReader, params.channelId, currentTaskId), params.taskResolutionTimeoutMs, `
|
|
327
|
+
const task = await withDeadline(findTaskForThread(params.taskReader, params.channelId, currentTaskId), params.taskResolutionTimeoutMs, `working folder resolution timed out for task ${currentTaskId}`);
|
|
328
328
|
if (!task) {
|
|
329
329
|
return {
|
|
330
330
|
taskAssignmentContext,
|
|
331
|
-
|
|
331
|
+
resolvedWorkingFolder: discussionOnlyWorkingFolder,
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
334
|
const executionLocalDirectory = task.properties[TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY];
|
|
@@ -337,7 +337,7 @@ async function resolveTaskThreadWorkspaceContext(params) {
|
|
|
337
337
|
|| !(await isExistingDirectory(params.fileSystem, executionLocalDirectory))) {
|
|
338
338
|
return {
|
|
339
339
|
taskAssignmentContext,
|
|
340
|
-
|
|
340
|
+
resolvedWorkingFolder: discussionOnlyWorkingFolder,
|
|
341
341
|
};
|
|
342
342
|
}
|
|
343
343
|
if (!params.allowCrossAgentIndependentWorkspaceHandoff
|
|
@@ -348,12 +348,12 @@ async function resolveTaskThreadWorkspaceContext(params) {
|
|
|
348
348
|
})) {
|
|
349
349
|
return {
|
|
350
350
|
taskAssignmentContext,
|
|
351
|
-
|
|
351
|
+
resolvedWorkingFolder: buildTaskThreadScratchResolvedWorkingFolder(params.workingFolderCollectionRootDir, params.channelId, 'cross-agent-independent-workspace-disabled'),
|
|
352
352
|
};
|
|
353
353
|
}
|
|
354
354
|
return {
|
|
355
355
|
taskAssignmentContext,
|
|
356
|
-
|
|
356
|
+
resolvedWorkingFolder: {
|
|
357
357
|
authority: 'task-execution-target',
|
|
358
358
|
taskId: task.id,
|
|
359
359
|
rootPath: executionLocalDirectory,
|
|
@@ -363,7 +363,7 @@ async function resolveTaskThreadWorkspaceContext(params) {
|
|
|
363
363
|
catch (error) {
|
|
364
364
|
return {
|
|
365
365
|
taskAssignmentContext,
|
|
366
|
-
|
|
366
|
+
resolvedWorkingFolder: discussionOnlyWorkingFolder,
|
|
367
367
|
};
|
|
368
368
|
}
|
|
369
369
|
}
|
|
@@ -383,32 +383,32 @@ export function resolveTaskAssignmentStateDirectory(stateRootDir, channelId) {
|
|
|
383
383
|
export function resolveTaskAssignmentStatePath(stateRootDir, channelId) {
|
|
384
384
|
return join(resolveTaskAssignmentStateDirectory(stateRootDir, channelId), TASK_ASSIGNMENT_STATE_FILENAME);
|
|
385
385
|
}
|
|
386
|
-
export function
|
|
387
|
-
return join(resolve(
|
|
386
|
+
export function resolveTaskWorkingFolderRootDirectory(channelRootDir) {
|
|
387
|
+
return join(resolve(channelRootDir), TASK_ISOLATED_WORKING_FOLDER_COLLECTION_DIRNAME);
|
|
388
388
|
}
|
|
389
|
-
export function
|
|
390
|
-
const explicitRoot =
|
|
389
|
+
export function resolveManagedWorkingFolderCollectionRoot(workingFolderCollectionRootDir, env = process.env, resolvedHomeDir = homedir()) {
|
|
390
|
+
const explicitRoot = workingFolderCollectionRootDir?.trim();
|
|
391
391
|
if (explicitRoot) {
|
|
392
392
|
return resolve(explicitRoot);
|
|
393
393
|
}
|
|
394
394
|
const home = env.HOME?.trim() || resolvedHomeDir.trim();
|
|
395
395
|
if (!home) {
|
|
396
|
-
throw new Error('Unable to resolve a user home directory for agents-host
|
|
396
|
+
throw new Error('Unable to resolve a user home directory for agents-host working folders');
|
|
397
397
|
}
|
|
398
|
-
return join(resolve(home), DEFAULT_HOME_BORGEE_DIRNAME,
|
|
398
|
+
return join(resolve(home), DEFAULT_HOME_BORGEE_DIRNAME, CHANNEL_WORKING_FOLDER_COLLECTION_DIRNAME);
|
|
399
399
|
}
|
|
400
|
-
export function
|
|
401
|
-
return join(resolve(
|
|
400
|
+
export function resolveChannelWorkingFolderRootDirectory(workingFolderCollectionRootDir, channelId) {
|
|
401
|
+
return join(resolve(workingFolderCollectionRootDir), encodeChannelPathSegment(channelId));
|
|
402
402
|
}
|
|
403
|
-
export function
|
|
404
|
-
return join(
|
|
403
|
+
export function resolveChannelWorkingFolderDirectory(workingFolderCollectionRootDir, channelId) {
|
|
404
|
+
return join(resolveChannelWorkingFolderRootDirectory(workingFolderCollectionRootDir, channelId), CHANNEL_WORKING_FOLDER_DIRNAME);
|
|
405
405
|
}
|
|
406
|
-
export function
|
|
407
|
-
return join(
|
|
406
|
+
export function resolveTaskIsolatedWorkingFolderDirectory(workingFolderCollectionRootDir, channelId, taskId) {
|
|
407
|
+
return join(resolveTaskWorkingFolderRootDirectory(resolveChannelWorkingFolderRootDirectory(workingFolderCollectionRootDir, channelId)), encodeChannelPathSegment(taskId), CHANNEL_WORKING_FOLDER_DIRNAME);
|
|
408
408
|
}
|
|
409
|
-
export const
|
|
410
|
-
export function
|
|
411
|
-
return join(
|
|
409
|
+
export const resolveTaskWorkingFolderDirectory = resolveTaskIsolatedWorkingFolderDirectory;
|
|
410
|
+
export function resolveTaskThreadScratchWorkingFolderDirectory(workingFolderCollectionRootDir, channelId) {
|
|
411
|
+
return join(resolveChannelWorkingFolderRootDirectory(workingFolderCollectionRootDir, channelId), TASK_THREAD_SCRATCH_WORKING_FOLDER_DIRNAME, CHANNEL_WORKING_FOLDER_DIRNAME);
|
|
412
412
|
}
|
|
413
413
|
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
414
414
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
@@ -509,7 +509,7 @@ export class FileChannelContextStore {
|
|
|
509
509
|
skillRuntimeEnabled;
|
|
510
510
|
skillAssetResolver;
|
|
511
511
|
localhostGateway;
|
|
512
|
-
|
|
512
|
+
workingFolderCollectionRootDir;
|
|
513
513
|
taskReader;
|
|
514
514
|
taskResolutionTimeoutMs;
|
|
515
515
|
allowCrossAgentIndependentWorkspaceHandoff;
|
|
@@ -520,9 +520,9 @@ export class FileChannelContextStore {
|
|
|
520
520
|
this.skillRuntimeEnabled = options.skillRuntimeEnabled ?? false;
|
|
521
521
|
this.skillAssetResolver = options.skillAssetResolver ?? new PackageSkillAssetResolver(import.meta.url, this.fileSystem);
|
|
522
522
|
this.localhostGateway = options.localhostGateway;
|
|
523
|
-
this.
|
|
523
|
+
this.workingFolderCollectionRootDir = resolveManagedWorkingFolderCollectionRoot(options.workingFolderCollectionRootDir, options.env, options.resolvedHomeDir);
|
|
524
524
|
this.taskReader = options.taskReader;
|
|
525
|
-
this.taskResolutionTimeoutMs = options.taskResolutionTimeoutMs ??
|
|
525
|
+
this.taskResolutionTimeoutMs = options.taskResolutionTimeoutMs ?? DEFAULT_TASK_WORKING_FOLDER_RESOLUTION_TIMEOUT_MS;
|
|
526
526
|
this.allowCrossAgentIndependentWorkspaceHandoff
|
|
527
527
|
= options.allowCrossAgentIndependentWorkspaceHandoff ?? true;
|
|
528
528
|
this.resolveStableAgentId = options.resolveStableAgentId;
|
|
@@ -571,9 +571,9 @@ export class FileChannelContextStore {
|
|
|
571
571
|
incomingMessageType: input.incomingMessageType,
|
|
572
572
|
incomingContent: input.incomingContent,
|
|
573
573
|
}, existingTaskAssignmentContext);
|
|
574
|
-
const
|
|
574
|
+
const workingFolderResolution = await resolveTaskThreadWorkingFolderContext({
|
|
575
575
|
channelId: input.channelId,
|
|
576
|
-
|
|
576
|
+
workingFolderCollectionRootDir: this.workingFolderCollectionRootDir,
|
|
577
577
|
taskAssignmentContext,
|
|
578
578
|
taskReader: this.taskReader,
|
|
579
579
|
taskResolutionTimeoutMs: this.taskResolutionTimeoutMs,
|
|
@@ -581,8 +581,8 @@ export class FileChannelContextStore {
|
|
|
581
581
|
resolveStableAgentId: this.resolveStableAgentId,
|
|
582
582
|
fileSystem: this.fileSystem,
|
|
583
583
|
});
|
|
584
|
-
const
|
|
585
|
-
const runtimeTaskAssignmentContext =
|
|
584
|
+
const resolvedWorkingFolder = workingFolderResolution.resolvedWorkingFolder;
|
|
585
|
+
const runtimeTaskAssignmentContext = workingFolderResolution.taskAssignmentContext ?? taskAssignmentContext;
|
|
586
586
|
const persistedTaskAssignmentContext = input.taskAssignmentContextOverridePersistence === 'ephemeral'
|
|
587
587
|
? existingTaskAssignmentContext
|
|
588
588
|
: runtimeTaskAssignmentContext;
|
|
@@ -591,12 +591,12 @@ export class FileChannelContextStore {
|
|
|
591
591
|
collaboration: input.collaboration,
|
|
592
592
|
taskAssignmentContext: runtimeTaskAssignmentContext,
|
|
593
593
|
});
|
|
594
|
-
const payload = buildChannelContextPayload(input.channelId, runtimeSurface, input.collaborationOutcome, input.attentionSnapshot, input.compactionSnapshot, input.taskThreadCollaborationContract, input.collaborationCapabilities, input.missedCollaborationDiagnostic, skillRuntime, localhostGateway, persistedTaskAssignmentContext ||
|
|
594
|
+
const payload = buildChannelContextPayload(input.channelId, runtimeSurface, input.collaborationOutcome, input.attentionSnapshot, input.compactionSnapshot, input.taskThreadCollaborationContract, input.collaborationCapabilities, input.missedCollaborationDiagnostic, skillRuntime, localhostGateway, persistedTaskAssignmentContext || resolvedWorkingFolder
|
|
595
595
|
? {
|
|
596
596
|
...(persistedTaskAssignmentContext
|
|
597
597
|
? { taskAssignmentContext: persistedTaskAssignmentContext }
|
|
598
598
|
: {}),
|
|
599
|
-
resolvedWorkspace,
|
|
599
|
+
resolvedWorkspace: resolvedWorkingFolder,
|
|
600
600
|
}
|
|
601
601
|
: undefined);
|
|
602
602
|
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
@@ -610,7 +610,7 @@ export class FileChannelContextStore {
|
|
|
610
610
|
gatewayCredentialPath: gatewayCredential ? gatewayCredentialPath : undefined,
|
|
611
611
|
skillRuntime,
|
|
612
612
|
localhostGateway,
|
|
613
|
-
|
|
613
|
+
resolvedWorkingFolder,
|
|
614
614
|
};
|
|
615
615
|
const shouldProjectClaudeBrief = input.provider === 'claude' && input.projectionStrategy === 'file-brief';
|
|
616
616
|
const claudeProjectedBriefContent = shouldProjectClaudeBrief
|
|
@@ -626,8 +626,8 @@ export class FileChannelContextStore {
|
|
|
626
626
|
try {
|
|
627
627
|
await this.fileSystem.mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
628
628
|
await this.fileSystem.mkdir(resolveTaskAssignmentStateDirectory(this.stateRootDir, input.channelId), { recursive: true, mode: 0o700 });
|
|
629
|
-
if (
|
|
630
|
-
await this.fileSystem.mkdir(
|
|
629
|
+
if (resolvedWorkingFolder.authority !== 'task-execution-target') {
|
|
630
|
+
await this.fileSystem.mkdir(resolvedWorkingFolder.rootPath, { recursive: true, mode: 0o700 });
|
|
631
631
|
}
|
|
632
632
|
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
633
633
|
if (!claudeProjectedBriefContent) {
|
|
@@ -675,9 +675,9 @@ export class FileChannelContextStore {
|
|
|
675
675
|
directoryPath,
|
|
676
676
|
payload: {
|
|
677
677
|
...payload,
|
|
678
|
-
...(
|
|
678
|
+
...(resolvedWorkingFolder.authority === 'task-execution-target' && runtimeTaskAssignmentContext?.active === true
|
|
679
679
|
? {
|
|
680
|
-
resolvedWorkspace:
|
|
680
|
+
resolvedWorkspace: buildTaskThreadScratchResolvedWorkingFolder(this.workingFolderCollectionRootDir, input.channelId),
|
|
681
681
|
}
|
|
682
682
|
: {}),
|
|
683
683
|
},
|
|
@@ -687,9 +687,9 @@ export class FileChannelContextStore {
|
|
|
687
687
|
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
688
688
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
689
689
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
690
|
-
|
|
691
|
-
?
|
|
692
|
-
:
|
|
690
|
+
resolvedWorkingFolder: resolvedWorkingFolder.authority === 'task-execution-target' && runtimeTaskAssignmentContext?.active === true
|
|
691
|
+
? buildTaskThreadScratchResolvedWorkingFolder(this.workingFolderCollectionRootDir, input.channelId)
|
|
692
|
+
: resolvedWorkingFolder,
|
|
693
693
|
};
|
|
694
694
|
throw new ChannelContextPreparationError('failed to persist channel context payload', partialContext, { cause: error });
|
|
695
695
|
}
|