@borgee/agents-host 0.2.44 → 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 +26 -5
- package/dist/agents-host.js +163 -192
- package/dist/chat/chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
- package/dist/chat/sdk-chat-control-plane.js +3 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +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 +39 -6
- package/dist/context/injection.js +317 -26
- 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 +456 -22
- package/dist/context/resolved-workspace.d.ts +2 -0
- package/dist/context/resolved-workspace.js +64 -0
- package/dist/context/skill-manual.d.ts +1 -0
- package/dist/context/skill-manual.js +4 -1
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +56 -14
- package/dist/gateway/localhost-gateway.js +2 -0
- package/dist/managed-daemon.js +122 -9
- 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/cli-client.d.ts +1 -2
- package/dist/providers/claude/cli-client.js +190 -117
- package/dist/providers/codex/cli-client.js +3 -83
- package/dist/providers/codex/project-doc.js +12 -11
- package/dist/providers/copilot/cli-client.js +3 -83
- package/dist/providers/create-provider.d.ts +2 -0
- package/dist/providers/create-provider.js +20 -4
- package/dist/state-paths.d.ts +1 -1
- package/dist/state-paths.js +3 -3
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +130 -6
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +9 -1
- package/skills/borgee-agent/references/task-properties.md +5 -2
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
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';
|
|
5
8
|
const CHANNEL_CONTEXT_ROOT_DIRNAME = 'channel-context';
|
|
6
9
|
const CHANNEL_CONTEXT_PAYLOAD_FILENAME = 'context.json';
|
|
7
10
|
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
8
|
-
const
|
|
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;
|
|
9
19
|
export class ChannelContextPreparationError extends Error {
|
|
10
20
|
partialContext;
|
|
11
21
|
constructor(message, partialContext, options) {
|
|
@@ -62,19 +72,50 @@ function buildBorgeeAgentGatewayCredential(channelId, localhostGateway) {
|
|
|
62
72
|
},
|
|
63
73
|
};
|
|
64
74
|
}
|
|
65
|
-
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) {
|
|
66
76
|
return {
|
|
67
77
|
schemaVersion: 1,
|
|
68
78
|
channelId,
|
|
79
|
+
runtimeSurface,
|
|
69
80
|
...(collaborationOutcome ? { collaborationOutcome } : {}),
|
|
70
81
|
...(attentionSnapshot ? { attentionSnapshot } : {}),
|
|
82
|
+
...(compactionSnapshot ? { compactionSnapshot } : {}),
|
|
71
83
|
...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
|
|
72
84
|
...(collaborationCapabilities ? { collaborationCapabilities } : {}),
|
|
73
85
|
...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
|
|
74
86
|
...(skillRuntime ? { skillRuntime: toSkillRuntimePayload(skillRuntime) } : {}),
|
|
75
87
|
...(localhostGateway ? { localhostGateway } : {}),
|
|
76
88
|
...(options?.taskAssignmentContext ? { taskAssignmentContext: options.taskAssignmentContext } : {}),
|
|
77
|
-
...(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,
|
|
78
119
|
};
|
|
79
120
|
}
|
|
80
121
|
function isRecord(value) {
|
|
@@ -96,6 +137,73 @@ function isUsableTaskId(value) {
|
|
|
96
137
|
&& !/[\u0000-\u001f\u007f\s]/u.test(value)
|
|
97
138
|
&& canRoundTripThroughUriComponentEncoding(value);
|
|
98
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
|
+
}
|
|
99
207
|
function sanitizeTaskAssignmentContext(value) {
|
|
100
208
|
if (!isRecord(value) || value.active !== true) {
|
|
101
209
|
return undefined;
|
|
@@ -133,6 +241,128 @@ async function readExistingTaskAssignmentContext(payloadPath, fileSystem) {
|
|
|
133
241
|
return undefined;
|
|
134
242
|
}
|
|
135
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
|
+
}
|
|
136
366
|
export function encodeChannelPathSegment(channelId) {
|
|
137
367
|
const encoded = Buffer.from(channelId, 'utf8').toString('hex');
|
|
138
368
|
return encoded.length > 0 ? encoded : 'empty';
|
|
@@ -144,17 +374,38 @@ export function resolveChannelContextPayloadPath(stateRootDir, channelId) {
|
|
|
144
374
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_CONTEXT_PAYLOAD_FILENAME);
|
|
145
375
|
}
|
|
146
376
|
export function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir) {
|
|
147
|
-
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));
|
|
148
392
|
}
|
|
149
|
-
export function
|
|
150
|
-
return join(
|
|
393
|
+
export function resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, channelId) {
|
|
394
|
+
return join(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId), CHANNEL_WORKSPACE_DIRNAME);
|
|
151
395
|
}
|
|
396
|
+
export function resolveTaskIsolatedWorkspaceDirectory(workspaceCollectionRootDir, channelId, taskId) {
|
|
397
|
+
return join(resolveTaskWorkspaceRootDirectory(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId)), encodeChannelPathSegment(taskId), CHANNEL_WORKSPACE_DIRNAME);
|
|
398
|
+
}
|
|
399
|
+
export const resolveTaskWorkspaceDirectory = resolveTaskIsolatedWorkspaceDirectory;
|
|
152
400
|
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
153
401
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
154
402
|
}
|
|
155
403
|
export function resolveGatewayCredentialPathFromPayloadPath(payloadPath) {
|
|
156
404
|
return join(dirname(payloadPath), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
157
405
|
}
|
|
406
|
+
export function resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath) {
|
|
407
|
+
return join(dirname(payloadPath), CLAUDE_PROJECTED_BRIEF_FILENAME);
|
|
408
|
+
}
|
|
158
409
|
/**
|
|
159
410
|
* Every basename a gateway credential can carry in a channel context directory, including the ones
|
|
160
411
|
* this host no longer writes. The directory holds at most one live credential — the token is
|
|
@@ -245,14 +496,18 @@ export class FileChannelContextStore {
|
|
|
245
496
|
skillRuntimeEnabled;
|
|
246
497
|
skillAssetResolver;
|
|
247
498
|
localhostGateway;
|
|
248
|
-
|
|
499
|
+
workspaceCollectionRootDir;
|
|
500
|
+
taskReader;
|
|
501
|
+
taskResolutionTimeoutMs;
|
|
249
502
|
constructor(stateRootDir, options = {}) {
|
|
250
503
|
this.stateRootDir = stateRootDir;
|
|
251
504
|
this.fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
|
|
252
505
|
this.skillRuntimeEnabled = options.skillRuntimeEnabled ?? false;
|
|
253
506
|
this.skillAssetResolver = options.skillAssetResolver ?? new PackageSkillAssetResolver(import.meta.url, this.fileSystem);
|
|
254
507
|
this.localhostGateway = options.localhostGateway;
|
|
255
|
-
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;
|
|
256
511
|
}
|
|
257
512
|
async prepare(inputOrChannelId, options) {
|
|
258
513
|
const input = typeof inputOrChannelId === 'string'
|
|
@@ -264,8 +519,10 @@ export class FileChannelContextStore {
|
|
|
264
519
|
const auxiliaryCollaborationEnabled = collaborationCommandsEnabled && collaborationRoutesAllowedForTurnMode;
|
|
265
520
|
const directoryPath = resolveChannelContextDirectory(this.stateRootDir, input.channelId);
|
|
266
521
|
const payloadPath = resolveChannelContextPayloadPath(this.stateRootDir, input.channelId);
|
|
522
|
+
const claudeProjectedBriefPath = resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath);
|
|
267
523
|
const gatewayCredentialPath = resolveGatewayCredentialPathFromPayloadPath(payloadPath);
|
|
268
524
|
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(payloadPath, this.fileSystem);
|
|
525
|
+
const existingResolvedWorkspace = await readExistingResolvedWorkspaceContext(payloadPath, this.fileSystem);
|
|
269
526
|
const skillRuntime = this.skillRuntimeEnabled
|
|
270
527
|
? await this.resolveSkillRuntimeBestEffort()
|
|
271
528
|
: undefined;
|
|
@@ -295,20 +552,31 @@ export class FileChannelContextStore {
|
|
|
295
552
|
incomingMessageType: input.incomingMessageType,
|
|
296
553
|
incomingContent: input.incomingContent,
|
|
297
554
|
}, existingTaskAssignmentContext);
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
:
|
|
304
|
-
|
|
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
|
|
305
570
|
? {
|
|
306
|
-
...(taskAssignmentContext
|
|
307
|
-
|
|
571
|
+
...(workspaceResolution.taskAssignmentContext
|
|
572
|
+
? { taskAssignmentContext: workspaceResolution.taskAssignmentContext }
|
|
573
|
+
: {}),
|
|
574
|
+
resolvedWorkspace,
|
|
308
575
|
}
|
|
309
576
|
: undefined);
|
|
310
577
|
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
311
|
-
let
|
|
578
|
+
let resolvedWorkspaceMaterialized = false;
|
|
579
|
+
let claudeProjectedBriefWritten = false;
|
|
312
580
|
let payloadWritten = false;
|
|
313
581
|
let gatewayCredentialWritten = false;
|
|
314
582
|
const preparedContext = {
|
|
@@ -318,15 +586,27 @@ export class FileChannelContextStore {
|
|
|
318
586
|
gatewayCredentialPath: gatewayCredential ? gatewayCredentialPath : undefined,
|
|
319
587
|
skillRuntime,
|
|
320
588
|
localhostGateway,
|
|
321
|
-
|
|
589
|
+
resolvedWorkspace,
|
|
322
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
|
+
}
|
|
323
602
|
try {
|
|
324
603
|
await this.fileSystem.mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
taskWorkspaceMaterialized = true;
|
|
328
|
-
}
|
|
604
|
+
await this.fileSystem.mkdir(resolvedWorkspace.rootPath, { recursive: true, mode: 0o700 });
|
|
605
|
+
resolvedWorkspaceMaterialized = true;
|
|
329
606
|
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
607
|
+
if (!claudeProjectedBriefContent) {
|
|
608
|
+
await unlinkIfPresent(this.fileSystem, claudeProjectedBriefPath);
|
|
609
|
+
}
|
|
330
610
|
await this.fileSystem.writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
331
611
|
payloadWritten = true;
|
|
332
612
|
if (gatewayCredential) {
|
|
@@ -342,20 +622,31 @@ export class FileChannelContextStore {
|
|
|
342
622
|
else {
|
|
343
623
|
this.localhostGateway?.clearChannel(input.channelId);
|
|
344
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
|
+
}
|
|
345
631
|
if (issuedLocalhostGateway) {
|
|
346
632
|
this.localhostGateway?.publishPayload(input.channelId, payloadPath, payload);
|
|
347
633
|
}
|
|
348
634
|
return preparedContext;
|
|
349
635
|
}
|
|
350
636
|
catch (error) {
|
|
637
|
+
if (claudeProjectedBriefContent && !claudeProjectedBriefWritten) {
|
|
638
|
+
await unlinkIfPresent(this.fileSystem, claudeProjectedBriefPath);
|
|
639
|
+
}
|
|
351
640
|
const partialContext = {
|
|
352
641
|
directoryPath,
|
|
353
642
|
payload,
|
|
643
|
+
claudeProjectedBriefPath: claudeProjectedBriefWritten ? claudeProjectedBriefPath : undefined,
|
|
644
|
+
claudeProjectedBriefHash: claudeProjectedBriefWritten ? preparedContext.claudeProjectedBriefHash : undefined,
|
|
354
645
|
payloadPath: payloadWritten ? payloadPath : undefined,
|
|
355
646
|
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
356
647
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
357
648
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
358
|
-
|
|
649
|
+
resolvedWorkspace: resolvedWorkspaceMaterialized ? resolvedWorkspace : undefined,
|
|
359
650
|
};
|
|
360
651
|
throw new ChannelContextPreparationError('failed to persist channel context payload', partialContext, { cause: error });
|
|
361
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 {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from '../projection-strategy-values.js';
|
|
2
|
+
import { findTaskForThread } from '../task-thread-resolution.js';
|
|
3
|
+
export class ServerProjectionStrategyResolver {
|
|
4
|
+
options;
|
|
5
|
+
strategyCache = new Map();
|
|
6
|
+
channelAliases = new Map();
|
|
7
|
+
fetchImpl;
|
|
8
|
+
fallback;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
12
|
+
this.fallback = options.fallback ?? (() => DEFAULT_PROJECTION_STRATEGY);
|
|
13
|
+
}
|
|
14
|
+
async resolve(channelId) {
|
|
15
|
+
const baseUrl = this.options.baseUrl?.trim();
|
|
16
|
+
const agentApiKey = this.options.agentApiKey?.trim();
|
|
17
|
+
const agentId = this.options.resolveStableAgentId?.()?.trim();
|
|
18
|
+
if (!baseUrl || !agentApiKey || !agentId) {
|
|
19
|
+
return this.fallback();
|
|
20
|
+
}
|
|
21
|
+
const cacheKey = `${agentId}::${channelId}`;
|
|
22
|
+
const url = new URL(`/api/v1/agents/${encodeURIComponent(agentId)}/channels/${encodeURIComponent(channelId)}/projection-strategy`, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
|
|
23
|
+
try {
|
|
24
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${agentApiKey}`,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
if (response.status === 404) {
|
|
30
|
+
const effectiveCacheKey = await this.resolveEffectiveCacheKey(agentId, channelId, cacheKey);
|
|
31
|
+
this.channelAliases.set(cacheKey, effectiveCacheKey);
|
|
32
|
+
this.strategyCache.set(effectiveCacheKey, DEFAULT_PROJECTION_STRATEGY);
|
|
33
|
+
return DEFAULT_PROJECTION_STRATEGY;
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`server returned ${response.status} ${response.statusText}`.trim());
|
|
37
|
+
}
|
|
38
|
+
const body = await response.json();
|
|
39
|
+
const strategy = normalizeProjectionStrategy(typeof body.projection_strategy === 'string' ? body.projection_strategy : undefined);
|
|
40
|
+
if (!strategy) {
|
|
41
|
+
throw new Error('server returned an invalid projection strategy');
|
|
42
|
+
}
|
|
43
|
+
const effectiveChannelId = typeof body.channel_id === 'string' && body.channel_id.length > 0
|
|
44
|
+
? body.channel_id
|
|
45
|
+
: channelId;
|
|
46
|
+
const effectiveCacheKey = `${agentId}::${effectiveChannelId}`;
|
|
47
|
+
this.channelAliases.set(cacheKey, effectiveCacheKey);
|
|
48
|
+
this.strategyCache.set(effectiveCacheKey, strategy);
|
|
49
|
+
return strategy;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const cached = await this.resolveCachedStrategy(agentId, channelId, cacheKey);
|
|
53
|
+
if (cached) {
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
this.options.logger?.error('failed to resolve server-backed projection strategy; using fallback', {
|
|
58
|
+
agentId,
|
|
59
|
+
channelId,
|
|
60
|
+
error: message,
|
|
61
|
+
});
|
|
62
|
+
return DEFAULT_PROJECTION_STRATEGY;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async resolveCachedStrategy(agentId, channelId, cacheKey) {
|
|
66
|
+
const direct = this.strategyCache.get(this.channelAliases.get(cacheKey) ?? cacheKey);
|
|
67
|
+
if (direct) {
|
|
68
|
+
return direct;
|
|
69
|
+
}
|
|
70
|
+
if (!this.options.taskReader) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const effectiveCacheKey = await this.resolveEffectiveCacheKey(agentId, channelId, cacheKey);
|
|
74
|
+
if (effectiveCacheKey === cacheKey) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
this.channelAliases.set(cacheKey, effectiveCacheKey);
|
|
78
|
+
return this.strategyCache.get(effectiveCacheKey);
|
|
79
|
+
}
|
|
80
|
+
async resolveEffectiveCacheKey(agentId, channelId, cacheKey) {
|
|
81
|
+
if (!this.options.taskReader) {
|
|
82
|
+
return cacheKey;
|
|
83
|
+
}
|
|
84
|
+
const task = await findTaskForThread(this.options.taskReader, channelId);
|
|
85
|
+
if (!task?.channelId) {
|
|
86
|
+
return cacheKey;
|
|
87
|
+
}
|
|
88
|
+
return `${agentId}::${task.channelId}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/context/prompt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PreparedPromptContext, ProviderKind } from '../types.js';
|
|
1
|
+
import type { ProjectionStrategy, PreparedPromptContext, ProviderKind } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Builds the per-turn prompt. Conversation memory is intentionally NOT
|
|
4
4
|
* assembled here: each provider's CLI client resumes a native per-channel
|
|
@@ -16,3 +16,18 @@ export declare function buildPrompt(params: {
|
|
|
16
16
|
incomingMessageType?: string;
|
|
17
17
|
promptContext?: PreparedPromptContext;
|
|
18
18
|
}): string;
|
|
19
|
+
export declare function buildClaudeSessionPromptAppend(params: {
|
|
20
|
+
agentName: string;
|
|
21
|
+
promptContext?: PreparedPromptContext;
|
|
22
|
+
promptStrategy?: ProjectionStrategy;
|
|
23
|
+
}): string | undefined;
|
|
24
|
+
export declare function buildClaudeTurnPrompt(params: {
|
|
25
|
+
agentName?: string;
|
|
26
|
+
channelId: string;
|
|
27
|
+
incomingAuthorId: string;
|
|
28
|
+
incomingContent: string;
|
|
29
|
+
incomingEventKind?: string;
|
|
30
|
+
incomingMessageType?: string;
|
|
31
|
+
promptContext?: PreparedPromptContext;
|
|
32
|
+
promptStrategy?: ProjectionStrategy;
|
|
33
|
+
}): string;
|