@borgee/agents-host 0.2.35 → 0.2.56
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 +23 -27
- package/dist/agents-host.d.ts +28 -5
- package/dist/agents-host.js +274 -236
- 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 +6 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +18 -1
- 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 +44 -9
- package/dist/context/injection.js +360 -53
- 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 +475 -53
- package/dist/context/resolved-workspace.d.ts +2 -0
- package/dist/context/resolved-workspace.js +64 -0
- package/dist/context/skill-manual.d.ts +14 -0
- package/dist/context/skill-manual.js +21 -0
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +68 -17
- package/dist/gateway/localhost-gateway.js +18 -8
- package/dist/hosted-turn-content.d.ts +15 -0
- package/dist/hosted-turn-content.js +50 -0
- package/dist/managed-daemon.d.ts +3 -2
- package/dist/managed-daemon.js +198 -37
- package/dist/plugin-sdk.js +276 -359
- package/dist/plugin-sdk.js.map +4 -4
- 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/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +10 -0
- package/dist/providers/claude/cli-client.d.ts +9 -1
- package/dist/providers/claude/cli-client.js +270 -126
- package/dist/providers/codex/adapter.d.ts +3 -1
- package/dist/providers/codex/adapter.js +10 -0
- package/dist/providers/codex/cli-client.d.ts +10 -2
- package/dist/providers/codex/cli-client.js +88 -104
- package/dist/providers/codex/project-doc.js +22 -37
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +10 -0
- package/dist/providers/copilot/cli-client.d.ts +9 -1
- package/dist/providers/copilot/cli-client.js +68 -85
- package/dist/providers/create-provider.d.ts +3 -1
- package/dist/providers/create-provider.js +36 -9
- package/dist/providers/provider-adapter.d.ts +35 -0
- package/dist/providers/provider-adapter.js +44 -1
- package/dist/state-paths.d.ts +10 -2
- package/dist/state-paths.js +25 -6
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +163 -8
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +127 -38
- package/skills/borgee-agent/references/errors.md +38 -0
- package/skills/borgee-agent/references/task-properties.md +33 -0
- package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
- package/skills/borgee-agent/borgee-agent.mjs +0 -562
- package/skills/borgee-agent/borgee-agent.py +0 -469
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
2
4
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
import { findTaskForThread } from '../task-thread-resolution.js';
|
|
7
|
+
import { buildClaudeFileBrief } from './claude-file-brief.js';
|
|
4
8
|
const CHANNEL_CONTEXT_ROOT_DIRNAME = 'channel-context';
|
|
5
9
|
const CHANNEL_CONTEXT_PAYLOAD_FILENAME = 'context.json';
|
|
6
|
-
const
|
|
7
|
-
const
|
|
10
|
+
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
11
|
+
const CLAUDE_PROJECTED_BRIEF_FILENAME = 'CLAUDE.md';
|
|
12
|
+
const DEFAULT_HOME_BORGEE_DIRNAME = '.borgee';
|
|
13
|
+
const CHANNEL_WORKSPACE_COLLECTION_DIRNAME = 'channels';
|
|
14
|
+
const CHANNEL_WORKSPACE_DIRNAME = 'workspace';
|
|
15
|
+
const TASK_ISOLATED_WORKSPACE_COLLECTION_DIRNAME = 'tasks';
|
|
16
|
+
const TASK_WORKSPACE_MODE_PROPERTY_KEY = 'workspace.mode';
|
|
17
|
+
const TASK_WORKSPACE_MODE_ISOLATED = 'isolated';
|
|
18
|
+
const DEFAULT_TASK_WORKSPACE_RESOLUTION_TIMEOUT_MS = 1_500;
|
|
8
19
|
export class ChannelContextPreparationError extends Error {
|
|
9
20
|
partialContext;
|
|
10
21
|
constructor(message, partialContext, options) {
|
|
@@ -29,6 +40,9 @@ const DEFAULT_FILE_SYSTEM = {
|
|
|
29
40
|
async unlink(path) {
|
|
30
41
|
await fs.unlink(path);
|
|
31
42
|
},
|
|
43
|
+
async rename(oldPath, newPath) {
|
|
44
|
+
await fs.rename(oldPath, newPath);
|
|
45
|
+
},
|
|
32
46
|
async access(path) {
|
|
33
47
|
await fs.access(path);
|
|
34
48
|
},
|
|
@@ -45,7 +59,7 @@ function toSkillRuntimePayload(skillRuntime) {
|
|
|
45
59
|
pythonCliPath: skillRuntime.pythonCliPath,
|
|
46
60
|
};
|
|
47
61
|
}
|
|
48
|
-
function
|
|
62
|
+
function buildBorgeeAgentGatewayCredential(channelId, localhostGateway) {
|
|
49
63
|
if (!localhostGateway) {
|
|
50
64
|
return undefined;
|
|
51
65
|
}
|
|
@@ -53,23 +67,55 @@ function buildLocalhostGatewayAuthPayload(channelId, localhostGateway) {
|
|
|
53
67
|
schemaVersion: 1,
|
|
54
68
|
channelId,
|
|
55
69
|
localhostGateway: {
|
|
70
|
+
baseUrl: localhostGateway.baseUrl,
|
|
56
71
|
token: localhostGateway.token,
|
|
57
72
|
},
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
|
-
function buildChannelContextPayload(channelId, collaborationOutcome, attentionSnapshot, taskThreadCollaborationContract, collaborationCapabilities, missedCollaborationDiagnostic, skillRuntime, localhostGateway, options) {
|
|
75
|
+
function buildChannelContextPayload(channelId, runtimeSurface, collaborationOutcome, attentionSnapshot, compactionSnapshot, taskThreadCollaborationContract, collaborationCapabilities, missedCollaborationDiagnostic, skillRuntime, localhostGateway, options) {
|
|
61
76
|
return {
|
|
62
77
|
schemaVersion: 1,
|
|
63
78
|
channelId,
|
|
79
|
+
runtimeSurface,
|
|
64
80
|
...(collaborationOutcome ? { collaborationOutcome } : {}),
|
|
65
81
|
...(attentionSnapshot ? { attentionSnapshot } : {}),
|
|
82
|
+
...(compactionSnapshot ? { compactionSnapshot } : {}),
|
|
66
83
|
...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
|
|
67
84
|
...(collaborationCapabilities ? { collaborationCapabilities } : {}),
|
|
68
85
|
...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
|
|
69
86
|
...(skillRuntime ? { skillRuntime: toSkillRuntimePayload(skillRuntime) } : {}),
|
|
70
87
|
...(localhostGateway ? { localhostGateway } : {}),
|
|
71
88
|
...(options?.taskAssignmentContext ? { taskAssignmentContext: options.taskAssignmentContext } : {}),
|
|
72
|
-
...(options?.
|
|
89
|
+
...(options?.resolvedWorkspace ? { resolvedWorkspace: options.resolvedWorkspace } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function hashUtf8Content(content) {
|
|
93
|
+
return createHash('sha256').update(content).digest('hex');
|
|
94
|
+
}
|
|
95
|
+
async function unlinkIfPresent(fileSystem, path) {
|
|
96
|
+
await fileSystem.unlink(path).catch((error) => {
|
|
97
|
+
if (error.code !== 'ENOENT') {
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function buildPromptContextForClaudeProjectedBrief(preparedContext) {
|
|
103
|
+
return {
|
|
104
|
+
channelContextPayloadPath: preparedContext.payloadPath,
|
|
105
|
+
claudeProjectedBriefPath: preparedContext.claudeProjectedBriefPath,
|
|
106
|
+
claudeProjectedBriefHash: preparedContext.claudeProjectedBriefHash,
|
|
107
|
+
gatewayCredentialPath: preparedContext.gatewayCredentialPath,
|
|
108
|
+
skillRuntime: preparedContext.skillRuntime,
|
|
109
|
+
localhostGateway: preparedContext.localhostGateway,
|
|
110
|
+
taskAssignmentContext: preparedContext.payload.taskAssignmentContext,
|
|
111
|
+
resolvedWorkspace: preparedContext.resolvedWorkspace,
|
|
112
|
+
runtimeSurface: preparedContext.payload.runtimeSurface,
|
|
113
|
+
collaborationOutcome: preparedContext.payload.collaborationOutcome,
|
|
114
|
+
attentionSnapshot: preparedContext.payload.attentionSnapshot,
|
|
115
|
+
compactionSnapshot: preparedContext.payload.compactionSnapshot,
|
|
116
|
+
collaborationCapabilities: preparedContext.payload.collaborationCapabilities,
|
|
117
|
+
missedCollaborationDiagnostic: preparedContext.payload.missedCollaborationDiagnostic,
|
|
118
|
+
taskThreadCollaborationContract: preparedContext.payload.taskThreadCollaborationContract,
|
|
73
119
|
};
|
|
74
120
|
}
|
|
75
121
|
function isRecord(value) {
|
|
@@ -91,6 +137,73 @@ function isUsableTaskId(value) {
|
|
|
91
137
|
&& !/[\u0000-\u001f\u007f\s]/u.test(value)
|
|
92
138
|
&& canRoundTripThroughUriComponentEncoding(value);
|
|
93
139
|
}
|
|
140
|
+
function normalizeTaskAssignmentCurrentTaskId(taskAssignmentContext) {
|
|
141
|
+
const currentTaskId = typeof taskAssignmentContext?.currentTaskId === 'string'
|
|
142
|
+
? taskAssignmentContext.currentTaskId.trim()
|
|
143
|
+
: '';
|
|
144
|
+
return currentTaskId && isUsableTaskId(currentTaskId)
|
|
145
|
+
? currentTaskId
|
|
146
|
+
: undefined;
|
|
147
|
+
}
|
|
148
|
+
export function buildRuntimeSurfaceForTurn(options) {
|
|
149
|
+
const turnMode = options?.collaboration?.turnMode ?? 'ordinary';
|
|
150
|
+
const gatewayAvailable = options?.localhostGateway !== undefined;
|
|
151
|
+
const currentTaskId = normalizeTaskAssignmentCurrentTaskId(options?.taskAssignmentContext);
|
|
152
|
+
const taskAssignmentThreadActive = options?.taskAssignmentContext?.active === true;
|
|
153
|
+
const collaborationGatewayEnabled = gatewayAvailable && options.localhostGateway?.collaboration?.enabled === true;
|
|
154
|
+
const auxiliarySendAvailable = turnMode === 'ordinary'
|
|
155
|
+
&& collaborationGatewayEnabled
|
|
156
|
+
&& options?.collaboration?.sendRoutesAllowed === true;
|
|
157
|
+
return {
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
turnMode,
|
|
160
|
+
gateway: {
|
|
161
|
+
available: gatewayAvailable,
|
|
162
|
+
readOnlyRoutesAuthorized: gatewayAvailable,
|
|
163
|
+
inspectBeforeAnswer: gatewayAvailable,
|
|
164
|
+
},
|
|
165
|
+
task: {
|
|
166
|
+
currentThread: taskAssignmentThreadActive ? 'task-assignment-thread' : 'parent-channel',
|
|
167
|
+
parentChannelTaskCollection: taskAssignmentThreadActive ? 'parent-channel-only' : 'available',
|
|
168
|
+
currentTaskShorthand: currentTaskId
|
|
169
|
+
? 'persisted-current-task'
|
|
170
|
+
: taskAssignmentThreadActive
|
|
171
|
+
? 'fallback-current-task'
|
|
172
|
+
: 'requires-task-id',
|
|
173
|
+
currentTaskIdPersisted: currentTaskId !== undefined,
|
|
174
|
+
currentTaskFallback: !currentTaskId && taskAssignmentThreadActive
|
|
175
|
+
? 'visible-parent-channel-thread-scan'
|
|
176
|
+
: 'disallowed',
|
|
177
|
+
},
|
|
178
|
+
collaboration: gatewayAvailable
|
|
179
|
+
? turnMode === 'ordinary'
|
|
180
|
+
? {
|
|
181
|
+
participantLookup: collaborationGatewayEnabled ? 'available' : 'unavailable',
|
|
182
|
+
draftRead: auxiliarySendAvailable ? 'available' : 'unavailable',
|
|
183
|
+
auxiliarySend: auxiliarySendAvailable ? 'available' : 'unavailable',
|
|
184
|
+
}
|
|
185
|
+
: {
|
|
186
|
+
participantLookup: 'unavailable',
|
|
187
|
+
draftRead: 'unavailable',
|
|
188
|
+
auxiliarySend: 'unavailable',
|
|
189
|
+
}
|
|
190
|
+
: {
|
|
191
|
+
participantLookup: 'unavailable',
|
|
192
|
+
draftRead: 'unavailable',
|
|
193
|
+
auxiliarySend: 'unavailable',
|
|
194
|
+
},
|
|
195
|
+
response: {
|
|
196
|
+
mainVisibleReply: turnMode === 'ordinary'
|
|
197
|
+
? 'model-visible-reply'
|
|
198
|
+
: turnMode === 'protocol-managed'
|
|
199
|
+
? 'host-visible-protocol-reply'
|
|
200
|
+
: 'no-visible-reply',
|
|
201
|
+
auxiliaryMessageUsage: auxiliarySendAvailable
|
|
202
|
+
? 'targeted-escalation-only'
|
|
203
|
+
: 'unavailable',
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
94
207
|
function sanitizeTaskAssignmentContext(value) {
|
|
95
208
|
if (!isRecord(value) || value.active !== true) {
|
|
96
209
|
return undefined;
|
|
@@ -128,6 +241,128 @@ async function readExistingTaskAssignmentContext(payloadPath, fileSystem) {
|
|
|
128
241
|
return undefined;
|
|
129
242
|
}
|
|
130
243
|
}
|
|
244
|
+
function sanitizeResolvedWorkspaceContext(value) {
|
|
245
|
+
if (!isRecord(value) || typeof value.rootPath !== 'string' || !isAbsolute(value.rootPath)) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
if (value.authority === 'channel'
|
|
249
|
+
&& typeof value.owningChannelId === 'string'
|
|
250
|
+
&& value.owningChannelId.length > 0) {
|
|
251
|
+
return {
|
|
252
|
+
authority: 'channel',
|
|
253
|
+
owningChannelId: value.owningChannelId,
|
|
254
|
+
rootPath: value.rootPath,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (value.authority === 'task'
|
|
258
|
+
&& typeof value.taskId === 'string'
|
|
259
|
+
&& isUsableTaskId(value.taskId.trim())) {
|
|
260
|
+
return {
|
|
261
|
+
authority: 'task',
|
|
262
|
+
taskId: value.taskId.trim(),
|
|
263
|
+
rootPath: value.rootPath,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
async function readExistingResolvedWorkspaceContext(payloadPath, fileSystem) {
|
|
269
|
+
try {
|
|
270
|
+
const raw = await fileSystem.readFile(payloadPath, { encoding: 'utf8' });
|
|
271
|
+
const payload = JSON.parse(raw);
|
|
272
|
+
return sanitizeResolvedWorkspaceContext(payload.resolvedWorkspace);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function buildChannelResolvedWorkspace(workspaceCollectionRootDir, channelId) {
|
|
279
|
+
return {
|
|
280
|
+
authority: 'channel',
|
|
281
|
+
owningChannelId: channelId,
|
|
282
|
+
rootPath: resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, channelId),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function buildStickyChannelResolvedWorkspace(workspaceCollectionRootDir, resolvedWorkspace) {
|
|
286
|
+
if (resolvedWorkspace?.authority !== 'channel') {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
const expectedRootPath = resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, resolvedWorkspace.owningChannelId);
|
|
290
|
+
if (resolvedWorkspace.rootPath !== expectedRootPath) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
authority: 'channel',
|
|
295
|
+
owningChannelId: resolvedWorkspace.owningChannelId,
|
|
296
|
+
rootPath: expectedRootPath,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function resolveTaskThreadWorkspaceContext(params) {
|
|
300
|
+
const channelWorkspace = buildChannelResolvedWorkspace(params.workspaceCollectionRootDir, params.channelId);
|
|
301
|
+
const taskAssignmentContext = params.taskAssignmentContext;
|
|
302
|
+
const currentTaskId = taskAssignmentContext?.currentTaskId?.trim();
|
|
303
|
+
if (taskAssignmentContext?.active !== true
|
|
304
|
+
|| !currentTaskId
|
|
305
|
+
|| !params.taskReader) {
|
|
306
|
+
return { taskAssignmentContext, resolvedWorkspace: channelWorkspace };
|
|
307
|
+
}
|
|
308
|
+
const stickyTaskWorkspace = params.existingResolvedWorkspace?.authority === 'task'
|
|
309
|
+
&& params.existingResolvedWorkspace.taskId === currentTaskId
|
|
310
|
+
? {
|
|
311
|
+
authority: 'task',
|
|
312
|
+
taskId: currentTaskId,
|
|
313
|
+
rootPath: resolveTaskWorkspaceDirectory(params.workspaceCollectionRootDir, params.channelId, currentTaskId),
|
|
314
|
+
}
|
|
315
|
+
: undefined;
|
|
316
|
+
const stickyChannelWorkspace = buildStickyChannelResolvedWorkspace(params.workspaceCollectionRootDir, params.existingResolvedWorkspace);
|
|
317
|
+
try {
|
|
318
|
+
const task = await withDeadline(findTaskForThread(params.taskReader, params.channelId, currentTaskId), params.taskResolutionTimeoutMs, `workspace resolution timed out for task ${currentTaskId}`);
|
|
319
|
+
if (!task) {
|
|
320
|
+
return {
|
|
321
|
+
taskAssignmentContext,
|
|
322
|
+
resolvedWorkspace: channelWorkspace,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async function withDeadline(promise, timeoutMs, timeoutMessage) {
|
|
326
|
+
if (timeoutMs <= 0) {
|
|
327
|
+
return await promise;
|
|
328
|
+
}
|
|
329
|
+
let timer;
|
|
330
|
+
try {
|
|
331
|
+
return await Promise.race([
|
|
332
|
+
promise,
|
|
333
|
+
new Promise((_, reject) => {
|
|
334
|
+
timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
|
335
|
+
}),
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
if (timer) {
|
|
340
|
+
clearTimeout(timer);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (task.properties[TASK_WORKSPACE_MODE_PROPERTY_KEY] !== TASK_WORKSPACE_MODE_ISOLATED) {
|
|
345
|
+
return {
|
|
346
|
+
taskAssignmentContext,
|
|
347
|
+
resolvedWorkspace: buildChannelResolvedWorkspace(params.workspaceCollectionRootDir, task.channelId),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
taskAssignmentContext,
|
|
352
|
+
resolvedWorkspace: {
|
|
353
|
+
authority: 'task',
|
|
354
|
+
taskId: task.id,
|
|
355
|
+
rootPath: resolveTaskWorkspaceDirectory(params.workspaceCollectionRootDir, params.channelId, task.id),
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return {
|
|
361
|
+
taskAssignmentContext,
|
|
362
|
+
resolvedWorkspace: stickyTaskWorkspace ?? stickyChannelWorkspace ?? channelWorkspace,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
131
366
|
export function encodeChannelPathSegment(channelId) {
|
|
132
367
|
const encoded = Buffer.from(channelId, 'utf8').toString('hex');
|
|
133
368
|
return encoded.length > 0 ? encoded : 'empty';
|
|
@@ -139,22 +374,54 @@ export function resolveChannelContextPayloadPath(stateRootDir, channelId) {
|
|
|
139
374
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_CONTEXT_PAYLOAD_FILENAME);
|
|
140
375
|
}
|
|
141
376
|
export function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir) {
|
|
142
|
-
return join(resolve(startupWorkspaceRootDir),
|
|
377
|
+
return join(resolve(startupWorkspaceRootDir), TASK_ISOLATED_WORKSPACE_COLLECTION_DIRNAME);
|
|
378
|
+
}
|
|
379
|
+
export function resolveManagedWorkspaceCollectionRoot(workspaceCollectionRootDir, env = process.env, resolvedHomeDir = homedir()) {
|
|
380
|
+
const explicitRoot = workspaceCollectionRootDir?.trim();
|
|
381
|
+
if (explicitRoot) {
|
|
382
|
+
return resolve(explicitRoot);
|
|
383
|
+
}
|
|
384
|
+
const home = env.HOME?.trim() || resolvedHomeDir.trim();
|
|
385
|
+
if (!home) {
|
|
386
|
+
throw new Error('Unable to resolve a user home directory for agents-host workspaces');
|
|
387
|
+
}
|
|
388
|
+
return join(resolve(home), DEFAULT_HOME_BORGEE_DIRNAME, CHANNEL_WORKSPACE_COLLECTION_DIRNAME);
|
|
389
|
+
}
|
|
390
|
+
export function resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId) {
|
|
391
|
+
return join(resolve(workspaceCollectionRootDir), encodeChannelPathSegment(channelId));
|
|
143
392
|
}
|
|
144
|
-
export function
|
|
145
|
-
return join(
|
|
393
|
+
export function resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, channelId) {
|
|
394
|
+
return join(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId), CHANNEL_WORKSPACE_DIRNAME);
|
|
146
395
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
? `.localhost-gateway-auth.${Buffer.from(turnExecutionId, 'utf8').toString('hex')}.json`
|
|
150
|
-
: CHANNEL_CONTEXT_GATEWAY_AUTH_FILENAME;
|
|
151
|
-
return join(resolveChannelContextDirectory(stateRootDir, channelId), filename);
|
|
396
|
+
export function resolveTaskIsolatedWorkspaceDirectory(workspaceCollectionRootDir, channelId, taskId) {
|
|
397
|
+
return join(resolveTaskWorkspaceRootDirectory(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId)), encodeChannelPathSegment(taskId), CHANNEL_WORKSPACE_DIRNAME);
|
|
152
398
|
}
|
|
153
|
-
export
|
|
154
|
-
|
|
399
|
+
export const resolveTaskWorkspaceDirectory = resolveTaskIsolatedWorkspaceDirectory;
|
|
400
|
+
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
401
|
+
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
155
402
|
}
|
|
156
|
-
|
|
157
|
-
|
|
403
|
+
export function resolveGatewayCredentialPathFromPayloadPath(payloadPath) {
|
|
404
|
+
return join(dirname(payloadPath), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
405
|
+
}
|
|
406
|
+
export function resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath) {
|
|
407
|
+
return join(dirname(payloadPath), CLAUDE_PROJECTED_BRIEF_FILENAME);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Every basename a gateway credential can carry in a channel context directory, including the ones
|
|
411
|
+
* this host no longer writes. The directory holds at most one live credential — the token is
|
|
412
|
+
* reissued every turn — and this sweep is the only thing that ever unlinks the others, so a name
|
|
413
|
+
* dropped from this list leaves a 0600 file behind that still looks live.
|
|
414
|
+
*/
|
|
415
|
+
const GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES = [
|
|
416
|
+
'.borgee-agent-gateway.',
|
|
417
|
+
'.borgee-agent-bootstrap.',
|
|
418
|
+
'.localhost-gateway-auth.',
|
|
419
|
+
];
|
|
420
|
+
export function isGatewayCredentialSidecarBasename(basename) {
|
|
421
|
+
return basename.endsWith('.json')
|
|
422
|
+
&& GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES.some((prefix) => basename.startsWith(prefix));
|
|
423
|
+
}
|
|
424
|
+
async function pruneGatewayCredentialSidecars(fileSystem, directoryPath, keepBasename) {
|
|
158
425
|
let entries;
|
|
159
426
|
try {
|
|
160
427
|
entries = await fileSystem.readdir(directoryPath);
|
|
@@ -167,8 +434,7 @@ async function pruneLegacyGatewayAuthSidecars(fileSystem, directoryPath) {
|
|
|
167
434
|
throw error;
|
|
168
435
|
}
|
|
169
436
|
await Promise.all(entries
|
|
170
|
-
.filter((entry) => entry
|
|
171
|
-
&& entry.endsWith('.json'))
|
|
437
|
+
.filter((entry) => isGatewayCredentialSidecarBasename(entry) && entry !== keepBasename)
|
|
172
438
|
.map(async (entry) => {
|
|
173
439
|
await fileSystem.unlink(join(directoryPath, entry)).catch((error) => {
|
|
174
440
|
if (error.code !== 'ENOENT') {
|
|
@@ -196,8 +462,8 @@ export async function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl, fileSystem
|
|
|
196
462
|
const packageRootPath = await resolvePackageRootFromModuleUrl(moduleUrl, fileSystem);
|
|
197
463
|
const skillDirectoryPath = join(packageRootPath, 'skills', BORGEE_AGENT_SKILL_DIRNAME);
|
|
198
464
|
const skillMarkdownPath = join(skillDirectoryPath, 'SKILL.md');
|
|
199
|
-
const nodeCliPath = join(skillDirectoryPath, 'borgee-agent.mjs');
|
|
200
|
-
const pythonCliPath = join(skillDirectoryPath, 'borgee-agent.py');
|
|
465
|
+
const nodeCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.mjs');
|
|
466
|
+
const pythonCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.py');
|
|
201
467
|
await Promise.all([
|
|
202
468
|
fileSystem.access(skillDirectoryPath),
|
|
203
469
|
fileSystem.access(skillMarkdownPath),
|
|
@@ -230,14 +496,18 @@ export class FileChannelContextStore {
|
|
|
230
496
|
skillRuntimeEnabled;
|
|
231
497
|
skillAssetResolver;
|
|
232
498
|
localhostGateway;
|
|
233
|
-
|
|
499
|
+
workspaceCollectionRootDir;
|
|
500
|
+
taskReader;
|
|
501
|
+
taskResolutionTimeoutMs;
|
|
234
502
|
constructor(stateRootDir, options = {}) {
|
|
235
503
|
this.stateRootDir = stateRootDir;
|
|
236
504
|
this.fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
|
|
237
505
|
this.skillRuntimeEnabled = options.skillRuntimeEnabled ?? false;
|
|
238
506
|
this.skillAssetResolver = options.skillAssetResolver ?? new PackageSkillAssetResolver(import.meta.url, this.fileSystem);
|
|
239
507
|
this.localhostGateway = options.localhostGateway;
|
|
240
|
-
this.
|
|
508
|
+
this.workspaceCollectionRootDir = resolveManagedWorkspaceCollectionRoot(options.workspaceCollectionRootDir, options.env, options.resolvedHomeDir);
|
|
509
|
+
this.taskReader = options.taskReader;
|
|
510
|
+
this.taskResolutionTimeoutMs = options.taskResolutionTimeoutMs ?? DEFAULT_TASK_WORKSPACE_RESOLUTION_TIMEOUT_MS;
|
|
241
511
|
}
|
|
242
512
|
async prepare(inputOrChannelId, options) {
|
|
243
513
|
const input = typeof inputOrChannelId === 'string'
|
|
@@ -249,8 +519,10 @@ export class FileChannelContextStore {
|
|
|
249
519
|
const auxiliaryCollaborationEnabled = collaborationCommandsEnabled && collaborationRoutesAllowedForTurnMode;
|
|
250
520
|
const directoryPath = resolveChannelContextDirectory(this.stateRootDir, input.channelId);
|
|
251
521
|
const payloadPath = resolveChannelContextPayloadPath(this.stateRootDir, input.channelId);
|
|
252
|
-
const
|
|
522
|
+
const claudeProjectedBriefPath = resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath);
|
|
523
|
+
const gatewayCredentialPath = resolveGatewayCredentialPathFromPayloadPath(payloadPath);
|
|
253
524
|
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(payloadPath, this.fileSystem);
|
|
525
|
+
const existingResolvedWorkspace = await readExistingResolvedWorkspaceContext(payloadPath, this.fileSystem);
|
|
254
526
|
const skillRuntime = this.skillRuntimeEnabled
|
|
255
527
|
? await this.resolveSkillRuntimeBestEffort()
|
|
256
528
|
: undefined;
|
|
@@ -280,66 +552,101 @@ export class FileChannelContextStore {
|
|
|
280
552
|
incomingMessageType: input.incomingMessageType,
|
|
281
553
|
incomingContent: input.incomingContent,
|
|
282
554
|
}, existingTaskAssignmentContext);
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
:
|
|
289
|
-
|
|
555
|
+
const workspaceResolution = await resolveTaskThreadWorkspaceContext({
|
|
556
|
+
channelId: input.channelId,
|
|
557
|
+
workspaceCollectionRootDir: this.workspaceCollectionRootDir,
|
|
558
|
+
taskAssignmentContext,
|
|
559
|
+
existingResolvedWorkspace,
|
|
560
|
+
taskReader: this.taskReader,
|
|
561
|
+
taskResolutionTimeoutMs: this.taskResolutionTimeoutMs,
|
|
562
|
+
});
|
|
563
|
+
const resolvedWorkspace = workspaceResolution.resolvedWorkspace;
|
|
564
|
+
const runtimeSurface = buildRuntimeSurfaceForTurn({
|
|
565
|
+
localhostGateway,
|
|
566
|
+
collaboration: input.collaboration,
|
|
567
|
+
taskAssignmentContext: workspaceResolution.taskAssignmentContext ?? taskAssignmentContext,
|
|
568
|
+
});
|
|
569
|
+
const payload = buildChannelContextPayload(input.channelId, runtimeSurface, input.collaborationOutcome, input.attentionSnapshot, input.compactionSnapshot, input.taskThreadCollaborationContract, input.collaborationCapabilities, input.missedCollaborationDiagnostic, skillRuntime, localhostGateway, workspaceResolution.taskAssignmentContext || resolvedWorkspace
|
|
290
570
|
? {
|
|
291
|
-
...(taskAssignmentContext
|
|
292
|
-
|
|
571
|
+
...(workspaceResolution.taskAssignmentContext
|
|
572
|
+
? { taskAssignmentContext: workspaceResolution.taskAssignmentContext }
|
|
573
|
+
: {}),
|
|
574
|
+
resolvedWorkspace,
|
|
293
575
|
}
|
|
294
576
|
: undefined);
|
|
295
|
-
const
|
|
296
|
-
let
|
|
577
|
+
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
578
|
+
let resolvedWorkspaceMaterialized = false;
|
|
579
|
+
let claudeProjectedBriefWritten = false;
|
|
297
580
|
let payloadWritten = false;
|
|
298
|
-
let
|
|
581
|
+
let gatewayCredentialWritten = false;
|
|
299
582
|
const preparedContext = {
|
|
300
583
|
directoryPath,
|
|
301
584
|
payload,
|
|
302
585
|
payloadPath,
|
|
303
|
-
|
|
586
|
+
gatewayCredentialPath: gatewayCredential ? gatewayCredentialPath : undefined,
|
|
304
587
|
skillRuntime,
|
|
305
588
|
localhostGateway,
|
|
306
|
-
|
|
589
|
+
resolvedWorkspace,
|
|
307
590
|
};
|
|
591
|
+
const shouldProjectClaudeBrief = input.provider === 'claude' && input.projectionStrategy === 'file-brief';
|
|
592
|
+
const claudeProjectedBriefContent = shouldProjectClaudeBrief
|
|
593
|
+
? buildClaudeFileBrief(buildPromptContextForClaudeProjectedBrief({
|
|
594
|
+
...preparedContext,
|
|
595
|
+
claudeProjectedBriefPath,
|
|
596
|
+
}))
|
|
597
|
+
: undefined;
|
|
598
|
+
if (claudeProjectedBriefContent) {
|
|
599
|
+
preparedContext.claudeProjectedBriefPath = claudeProjectedBriefPath;
|
|
600
|
+
preparedContext.claudeProjectedBriefHash = hashUtf8Content(claudeProjectedBriefContent);
|
|
601
|
+
}
|
|
308
602
|
try {
|
|
309
603
|
await this.fileSystem.mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
604
|
+
await this.fileSystem.mkdir(resolvedWorkspace.rootPath, { recursive: true, mode: 0o700 });
|
|
605
|
+
resolvedWorkspaceMaterialized = true;
|
|
606
|
+
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
607
|
+
if (!claudeProjectedBriefContent) {
|
|
608
|
+
await unlinkIfPresent(this.fileSystem, claudeProjectedBriefPath);
|
|
313
609
|
}
|
|
314
|
-
await pruneLegacyGatewayAuthSidecars(this.fileSystem, directoryPath);
|
|
315
610
|
await this.fileSystem.writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
316
611
|
payloadWritten = true;
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
612
|
+
if (gatewayCredential) {
|
|
613
|
+
// Replaced atomically: a CLI invocation left over from an earlier turn must read either
|
|
614
|
+
// the old credential or the new one, never a missing or half-written file, because the
|
|
615
|
+
// documented correction for a rotated token is a 401 and not a usage error. The staging
|
|
616
|
+
// name is itself a credential sidecar, so a crashed run's leftover is swept above.
|
|
617
|
+
const stagingCredentialPath = join(directoryPath, `.borgee-agent-gateway.${randomUUID()}.json`);
|
|
618
|
+
await this.fileSystem.writeFile(stagingCredentialPath, `${JSON.stringify(gatewayCredential, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
619
|
+
await this.fileSystem.rename(stagingCredentialPath, gatewayCredentialPath);
|
|
620
|
+
gatewayCredentialWritten = true;
|
|
320
621
|
}
|
|
321
622
|
else {
|
|
322
|
-
await this.fileSystem.unlink(gatewayAuthPath).catch((error) => {
|
|
323
|
-
if (error.code !== 'ENOENT') {
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
623
|
this.localhostGateway?.clearChannel(input.channelId);
|
|
328
624
|
}
|
|
625
|
+
if (claudeProjectedBriefContent) {
|
|
626
|
+
const stagingProjectedBriefPath = join(directoryPath, `.claude-file-brief.${randomUUID()}.md`);
|
|
627
|
+
await this.fileSystem.writeFile(stagingProjectedBriefPath, claudeProjectedBriefContent, { encoding: 'utf8', mode: 0o600 });
|
|
628
|
+
await this.fileSystem.rename(stagingProjectedBriefPath, claudeProjectedBriefPath);
|
|
629
|
+
claudeProjectedBriefWritten = true;
|
|
630
|
+
}
|
|
329
631
|
if (issuedLocalhostGateway) {
|
|
330
632
|
this.localhostGateway?.publishPayload(input.channelId, payloadPath, payload);
|
|
331
633
|
}
|
|
332
634
|
return preparedContext;
|
|
333
635
|
}
|
|
334
636
|
catch (error) {
|
|
637
|
+
if (claudeProjectedBriefContent && !claudeProjectedBriefWritten) {
|
|
638
|
+
await unlinkIfPresent(this.fileSystem, claudeProjectedBriefPath);
|
|
639
|
+
}
|
|
335
640
|
const partialContext = {
|
|
336
641
|
directoryPath,
|
|
337
642
|
payload,
|
|
643
|
+
claudeProjectedBriefPath: claudeProjectedBriefWritten ? claudeProjectedBriefPath : undefined,
|
|
644
|
+
claudeProjectedBriefHash: claudeProjectedBriefWritten ? preparedContext.claudeProjectedBriefHash : undefined,
|
|
338
645
|
payloadPath: payloadWritten ? payloadPath : undefined,
|
|
339
|
-
|
|
646
|
+
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
340
647
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
341
648
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
342
|
-
|
|
649
|
+
resolvedWorkspace: resolvedWorkspaceMaterialized ? resolvedWorkspace : undefined,
|
|
343
650
|
};
|
|
344
651
|
throw new ChannelContextPreparationError('failed to persist channel context payload', partialContext, { cause: error });
|
|
345
652
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MAIN_SESSION_DELEGATION_LINES: readonly
|
|
1
|
+
export declare const MAIN_SESSION_DELEGATION_LINES: readonly ["This session is your coordination surface: read the channel, answer the human, break work down, and keep task state current here.", "Delegate the actual work to subagents — research, code edits, long builds and test runs, and broad repository sweeps belong in a delegated worker instead of running inline here.", "Delegation changes who executes, not who owns: the work still belongs to this thread, and you still report the result here yourself.", "Stay responsive while delegated work runs, so a new incoming message never waits behind a long local operation."];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DebugLogger } from '../debug.js';
|
|
2
|
+
import type { ChatControlPlane } from '../chat/chat-control-plane.js';
|
|
3
|
+
import type { ProjectionStrategy } from '../types.js';
|
|
4
|
+
interface ProjectionStrategyResolverOptions {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
agentApiKey?: string;
|
|
7
|
+
resolveStableAgentId?: () => string | undefined;
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
fallback?: () => ProjectionStrategy;
|
|
10
|
+
logger?: Pick<DebugLogger, 'error'>;
|
|
11
|
+
taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks'>;
|
|
12
|
+
}
|
|
13
|
+
export declare class ServerProjectionStrategyResolver {
|
|
14
|
+
private readonly options;
|
|
15
|
+
private readonly strategyCache;
|
|
16
|
+
private readonly channelAliases;
|
|
17
|
+
private readonly fetchImpl;
|
|
18
|
+
private readonly fallback;
|
|
19
|
+
constructor(options: ProjectionStrategyResolverOptions);
|
|
20
|
+
resolve(channelId: string): Promise<ProjectionStrategy>;
|
|
21
|
+
private resolveCachedStrategy;
|
|
22
|
+
private resolveEffectiveCacheKey;
|
|
23
|
+
}
|
|
24
|
+
export {};
|