@borgee/agents-host 0.2.56 → 0.2.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/dist/agents-host.d.ts +4 -1
- package/dist/agents-host.js +229 -20
- package/dist/chat/chat-control-plane.d.ts +2 -1
- package/dist/chat/sdk-chat-control-plane.d.ts +3 -2
- package/dist/chat/sdk-chat-control-plane.js +4 -0
- package/dist/compatibility-gates.js +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +21 -0
- package/dist/context/injection.d.ts +20 -2
- package/dist/context/injection.js +146 -103
- package/dist/context/prompt.js +11 -7
- package/dist/context/resolved-workspace.d.ts +1 -0
- package/dist/context/resolved-workspace.js +50 -8
- package/dist/context/turn-preparation.js +8 -0
- package/dist/gateway/localhost-gateway.js +2 -5
- package/dist/local-config.js +11 -1
- package/dist/managed-daemon.js +18 -13
- package/dist/plugin-sdk.js +0 -17
- package/dist/plugin-sdk.js.map +2 -2
- package/dist/policy/copilot-permission.d.ts +1 -0
- package/dist/policy/copilot-permission.js +18 -0
- package/dist/policy/gateway-authorization.js +2 -2
- package/dist/providers/claude/adapter.d.ts +1 -0
- package/dist/providers/claude/adapter.js +3 -0
- package/dist/providers/claude/cli-client.d.ts +2 -1
- package/dist/providers/claude/cli-client.js +53 -3
- package/dist/providers/codex/adapter.d.ts +1 -0
- package/dist/providers/codex/adapter.js +3 -0
- package/dist/providers/codex/cli-client.d.ts +3 -0
- package/dist/providers/codex/cli-client.js +50 -22
- package/dist/providers/copilot/adapter.d.ts +1 -0
- package/dist/providers/copilot/adapter.js +3 -0
- package/dist/providers/copilot/cli-client.d.ts +2 -1
- package/dist/providers/copilot/cli-client.js +30 -3
- package/dist/providers/create-provider.d.ts +1 -1
- package/dist/providers/create-provider.js +8 -0
- package/dist/providers/provider-adapter.d.ts +7 -0
- package/dist/providers/provider-adapter.js +8 -0
- package/dist/types.d.ts +16 -3
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +4 -4
- package/skills/borgee-agent/references/errors.md +2 -2
- package/skills/borgee-agent/references/task-properties.md +4 -3
|
@@ -49,6 +49,8 @@ export interface ChannelContextStore {
|
|
|
49
49
|
missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
|
|
50
50
|
incomingMessageType?: string;
|
|
51
51
|
incomingContent?: string;
|
|
52
|
+
taskAssignmentContextOverride?: TaskAssignmentThreadContext;
|
|
53
|
+
taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
|
|
52
54
|
}): Promise<PreparedChannelContext>;
|
|
53
55
|
prepare(channelId: string, options?: {
|
|
54
56
|
provider?: ProviderKind;
|
|
@@ -62,6 +64,8 @@ export interface ChannelContextStore {
|
|
|
62
64
|
missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
|
|
63
65
|
incomingMessageType?: string;
|
|
64
66
|
incomingContent?: string;
|
|
67
|
+
taskAssignmentContextOverride?: TaskAssignmentThreadContext;
|
|
68
|
+
taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
|
|
65
69
|
}): Promise<PreparedChannelContext>;
|
|
66
70
|
}
|
|
67
71
|
export interface SkillAssetResolver {
|
|
@@ -95,7 +99,10 @@ interface ContextFileSystem {
|
|
|
95
99
|
}): Promise<void>;
|
|
96
100
|
unlink(path: string): Promise<void>;
|
|
97
101
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
98
|
-
access(path: string): Promise<void>;
|
|
102
|
+
access(path: string, mode?: number): Promise<void>;
|
|
103
|
+
stat?(path: string): Promise<{
|
|
104
|
+
isDirectory(): boolean;
|
|
105
|
+
}>;
|
|
99
106
|
}
|
|
100
107
|
interface FileChannelContextStoreOptions {
|
|
101
108
|
fileSystem?: ContextFileSystem;
|
|
@@ -103,8 +110,10 @@ interface FileChannelContextStoreOptions {
|
|
|
103
110
|
skillAssetResolver?: SkillAssetResolver;
|
|
104
111
|
localhostGateway?: LocalhostGatewayContextPublisher;
|
|
105
112
|
workspaceCollectionRootDir?: string;
|
|
106
|
-
taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks'>;
|
|
113
|
+
taskReader?: Pick<ChatControlPlane, 'getTask' | 'listChannels' | 'listTasks' | 'listUsers'>;
|
|
107
114
|
taskResolutionTimeoutMs?: number;
|
|
115
|
+
allowCrossAgentIndependentWorkspaceHandoff?: boolean;
|
|
116
|
+
resolveStableAgentId?: () => string | undefined;
|
|
108
117
|
env?: NodeJS.ProcessEnv;
|
|
109
118
|
resolvedHomeDir?: string;
|
|
110
119
|
}
|
|
@@ -117,12 +126,15 @@ export declare function extractTaskIdFromTaskAssignmentContent(incomingContent:
|
|
|
117
126
|
export declare function encodeChannelPathSegment(channelId: string): string;
|
|
118
127
|
export declare function resolveChannelContextDirectory(stateRootDir: string, channelId: string): string;
|
|
119
128
|
export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
|
|
129
|
+
export declare function resolveTaskAssignmentStateDirectory(stateRootDir: string, channelId: string): string;
|
|
130
|
+
export declare function resolveTaskAssignmentStatePath(stateRootDir: string, channelId: string): string;
|
|
120
131
|
export declare function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir: string): string;
|
|
121
132
|
export declare function resolveManagedWorkspaceCollectionRoot(workspaceCollectionRootDir: string | undefined, env?: NodeJS.ProcessEnv, resolvedHomeDir?: string): string;
|
|
122
133
|
export declare function resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir: string, channelId: string): string;
|
|
123
134
|
export declare function resolveChannelWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string): string;
|
|
124
135
|
export declare function resolveTaskIsolatedWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string, taskId: string): string;
|
|
125
136
|
export declare const resolveTaskWorkspaceDirectory: typeof resolveTaskIsolatedWorkspaceDirectory;
|
|
137
|
+
export declare function resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRootDir: string, channelId: string): string;
|
|
126
138
|
export declare function resolveChannelGatewayCredentialPath(stateRootDir: string, channelId: string): string;
|
|
127
139
|
export declare function resolveGatewayCredentialPathFromPayloadPath(payloadPath: string): string;
|
|
128
140
|
export declare function resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath: string): string;
|
|
@@ -137,6 +149,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
|
|
|
137
149
|
private readonly workspaceCollectionRootDir;
|
|
138
150
|
private readonly taskReader?;
|
|
139
151
|
private readonly taskResolutionTimeoutMs;
|
|
152
|
+
private readonly allowCrossAgentIndependentWorkspaceHandoff;
|
|
153
|
+
private readonly resolveStableAgentId?;
|
|
140
154
|
constructor(stateRootDir: string, options?: FileChannelContextStoreOptions);
|
|
141
155
|
prepare(inputOrChannelId: {
|
|
142
156
|
channelId: string;
|
|
@@ -151,6 +165,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
|
|
|
151
165
|
missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
|
|
152
166
|
incomingMessageType?: string;
|
|
153
167
|
incomingContent?: string;
|
|
168
|
+
taskAssignmentContextOverride?: TaskAssignmentThreadContext;
|
|
169
|
+
taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
|
|
154
170
|
} | string, options?: {
|
|
155
171
|
provider?: ProviderKind;
|
|
156
172
|
projectionStrategy?: ProjectionStrategy;
|
|
@@ -163,6 +179,8 @@ export declare class FileChannelContextStore implements ChannelContextStore {
|
|
|
163
179
|
missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
|
|
164
180
|
incomingMessageType?: string;
|
|
165
181
|
incomingContent?: string;
|
|
182
|
+
taskAssignmentContextOverride?: TaskAssignmentThreadContext;
|
|
183
|
+
taskAssignmentContextOverridePersistence?: 'persist' | 'ephemeral';
|
|
166
184
|
}): Promise<PreparedChannelContext>;
|
|
167
185
|
private resolveSkillRuntimeBestEffort;
|
|
168
186
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
1
|
+
import { constants as fsConstants, promises as fs } from 'node:fs';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -7,14 +7,16 @@ import { findTaskForThread } from '../task-thread-resolution.js';
|
|
|
7
7
|
import { buildClaudeFileBrief } from './claude-file-brief.js';
|
|
8
8
|
const CHANNEL_CONTEXT_ROOT_DIRNAME = 'channel-context';
|
|
9
9
|
const CHANNEL_CONTEXT_PAYLOAD_FILENAME = 'context.json';
|
|
10
|
+
const TASK_ASSIGNMENT_STATE_ROOT_DIRNAME = 'task-assignment-state';
|
|
11
|
+
const TASK_ASSIGNMENT_STATE_FILENAME = 'context.json';
|
|
10
12
|
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
11
13
|
const CLAUDE_PROJECTED_BRIEF_FILENAME = 'CLAUDE.md';
|
|
12
14
|
const DEFAULT_HOME_BORGEE_DIRNAME = '.borgee';
|
|
13
15
|
const CHANNEL_WORKSPACE_COLLECTION_DIRNAME = 'channels';
|
|
14
16
|
const CHANNEL_WORKSPACE_DIRNAME = 'workspace';
|
|
15
17
|
const TASK_ISOLATED_WORKSPACE_COLLECTION_DIRNAME = 'tasks';
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
+
const TASK_THREAD_SCRATCH_WORKSPACE_DIRNAME = 'scratch';
|
|
19
|
+
const TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY = 'execution.local_directory';
|
|
18
20
|
const DEFAULT_TASK_WORKSPACE_RESOLUTION_TIMEOUT_MS = 1_500;
|
|
19
21
|
export class ChannelContextPreparationError extends Error {
|
|
20
22
|
partialContext;
|
|
@@ -43,8 +45,11 @@ const DEFAULT_FILE_SYSTEM = {
|
|
|
43
45
|
async rename(oldPath, newPath) {
|
|
44
46
|
await fs.rename(oldPath, newPath);
|
|
45
47
|
},
|
|
46
|
-
async access(path) {
|
|
47
|
-
await fs.access(path);
|
|
48
|
+
async access(path, mode) {
|
|
49
|
+
await fs.access(path, mode);
|
|
50
|
+
},
|
|
51
|
+
async stat(path) {
|
|
52
|
+
return await fs.stat(path);
|
|
48
53
|
},
|
|
49
54
|
};
|
|
50
55
|
const BORGEE_AGENT_SKILL_DIRNAME = 'borgee-agent';
|
|
@@ -231,9 +236,9 @@ function buildTaskAssignmentContextForTurn(options, existingContext) {
|
|
|
231
236
|
}
|
|
232
237
|
return existingContext;
|
|
233
238
|
}
|
|
234
|
-
async function readExistingTaskAssignmentContext(
|
|
239
|
+
async function readExistingTaskAssignmentContext(statePath, fileSystem) {
|
|
235
240
|
try {
|
|
236
|
-
const raw = await fileSystem.readFile(
|
|
241
|
+
const raw = await fileSystem.readFile(statePath, { encoding: 'utf8' });
|
|
237
242
|
const payload = JSON.parse(raw);
|
|
238
243
|
return sanitizeTaskAssignmentContext(payload.taskAssignmentContext);
|
|
239
244
|
}
|
|
@@ -241,125 +246,124 @@ async function readExistingTaskAssignmentContext(payloadPath, fileSystem) {
|
|
|
241
246
|
return undefined;
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
function buildChannelResolvedWorkspace(workspaceCollectionRootDir, channelId) {
|
|
250
|
+
return {
|
|
251
|
+
authority: 'channel',
|
|
252
|
+
owningChannelId: channelId,
|
|
253
|
+
rootPath: resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, channelId),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function buildTaskThreadScratchResolvedWorkspace(workspaceCollectionRootDir, channelId, reason = 'missing-or-invalid-execution-target') {
|
|
257
|
+
return {
|
|
258
|
+
authority: 'task-thread-scratch',
|
|
259
|
+
rootPath: resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRootDir, channelId),
|
|
260
|
+
reason,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function isCrossAgentTaskCreatedByAnotherAgent(params) {
|
|
264
|
+
const selfAgentId = params.resolveStableAgentId?.()?.trim();
|
|
265
|
+
if (!selfAgentId) {
|
|
266
|
+
return true;
|
|
247
267
|
}
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
&& value.owningChannelId.length > 0) {
|
|
251
|
-
return {
|
|
252
|
-
authority: 'channel',
|
|
253
|
-
owningChannelId: value.owningChannelId,
|
|
254
|
-
rootPath: value.rootPath,
|
|
255
|
-
};
|
|
268
|
+
if (params.task.createdBy === selfAgentId) {
|
|
269
|
+
return false;
|
|
256
270
|
}
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
&& isUsableTaskId(value.taskId.trim())) {
|
|
260
|
-
return {
|
|
261
|
-
authority: 'task',
|
|
262
|
-
taskId: value.taskId.trim(),
|
|
263
|
-
rootPath: value.rootPath,
|
|
264
|
-
};
|
|
271
|
+
if (!params.taskReader) {
|
|
272
|
+
return true;
|
|
265
273
|
}
|
|
266
|
-
|
|
274
|
+
const users = await params.taskReader.listUsers();
|
|
275
|
+
const creator = users.find((user) => user.id === params.task.createdBy);
|
|
276
|
+
if (!creator) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
return creator.kind === 'agent';
|
|
267
280
|
}
|
|
268
|
-
async function
|
|
281
|
+
async function withDeadline(promise, timeoutMs, timeoutMessage) {
|
|
282
|
+
if (timeoutMs <= 0) {
|
|
283
|
+
return await promise;
|
|
284
|
+
}
|
|
285
|
+
let timer;
|
|
269
286
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
return await Promise.race([
|
|
288
|
+
promise,
|
|
289
|
+
new Promise((_, reject) => {
|
|
290
|
+
timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
|
291
|
+
}),
|
|
292
|
+
]);
|
|
273
293
|
}
|
|
274
|
-
|
|
275
|
-
|
|
294
|
+
finally {
|
|
295
|
+
if (timer) {
|
|
296
|
+
clearTimeout(timer);
|
|
297
|
+
}
|
|
276
298
|
}
|
|
277
299
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
owningChannelId: channelId,
|
|
282
|
-
rootPath: resolveChannelWorkspaceDirectory(workspaceCollectionRootDir, channelId),
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
function buildStickyChannelResolvedWorkspace(workspaceCollectionRootDir, resolvedWorkspace) {
|
|
286
|
-
if (resolvedWorkspace?.authority !== 'channel') {
|
|
287
|
-
return undefined;
|
|
300
|
+
async function isExistingDirectory(fileSystem, path) {
|
|
301
|
+
if (!isAbsolute(path)) {
|
|
302
|
+
return false;
|
|
288
303
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
304
|
+
try {
|
|
305
|
+
await fileSystem.access(path, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK);
|
|
306
|
+
if (typeof fileSystem.stat !== 'function') {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
return (await fileSystem.stat(path)).isDirectory();
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return false;
|
|
292
313
|
}
|
|
293
|
-
return {
|
|
294
|
-
authority: 'channel',
|
|
295
|
-
owningChannelId: resolvedWorkspace.owningChannelId,
|
|
296
|
-
rootPath: expectedRootPath,
|
|
297
|
-
};
|
|
298
314
|
}
|
|
299
315
|
async function resolveTaskThreadWorkspaceContext(params) {
|
|
300
316
|
const channelWorkspace = buildChannelResolvedWorkspace(params.workspaceCollectionRootDir, params.channelId);
|
|
317
|
+
const discussionOnlyWorkspace = buildTaskThreadScratchResolvedWorkspace(params.workspaceCollectionRootDir, params.channelId);
|
|
301
318
|
const taskAssignmentContext = params.taskAssignmentContext;
|
|
302
319
|
const currentTaskId = taskAssignmentContext?.currentTaskId?.trim();
|
|
303
|
-
if (taskAssignmentContext?.active !== true
|
|
304
|
-
|| !currentTaskId
|
|
305
|
-
|| !params.taskReader) {
|
|
320
|
+
if (taskAssignmentContext?.active !== true) {
|
|
306
321
|
return { taskAssignmentContext, resolvedWorkspace: channelWorkspace };
|
|
307
322
|
}
|
|
308
|
-
|
|
309
|
-
|
|
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);
|
|
323
|
+
if (!currentTaskId || !params.taskReader) {
|
|
324
|
+
return { taskAssignmentContext, resolvedWorkspace: discussionOnlyWorkspace };
|
|
325
|
+
}
|
|
317
326
|
try {
|
|
318
327
|
const task = await withDeadline(findTaskForThread(params.taskReader, params.channelId, currentTaskId), params.taskResolutionTimeoutMs, `workspace resolution timed out for task ${currentTaskId}`);
|
|
319
328
|
if (!task) {
|
|
320
329
|
return {
|
|
321
330
|
taskAssignmentContext,
|
|
322
|
-
resolvedWorkspace:
|
|
331
|
+
resolvedWorkspace: discussionOnlyWorkspace,
|
|
323
332
|
};
|
|
324
333
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
}
|
|
334
|
+
const executionLocalDirectory = task.properties[TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY];
|
|
335
|
+
if (typeof executionLocalDirectory !== 'string'
|
|
336
|
+
|| executionLocalDirectory.length === 0
|
|
337
|
+
|| !(await isExistingDirectory(params.fileSystem, executionLocalDirectory))) {
|
|
338
|
+
return {
|
|
339
|
+
taskAssignmentContext,
|
|
340
|
+
resolvedWorkspace: discussionOnlyWorkspace,
|
|
341
|
+
};
|
|
343
342
|
}
|
|
344
|
-
if (
|
|
343
|
+
if (!params.allowCrossAgentIndependentWorkspaceHandoff
|
|
344
|
+
&& await isCrossAgentTaskCreatedByAnotherAgent({
|
|
345
|
+
task,
|
|
346
|
+
taskReader: params.taskReader,
|
|
347
|
+
resolveStableAgentId: params.resolveStableAgentId,
|
|
348
|
+
})) {
|
|
345
349
|
return {
|
|
346
350
|
taskAssignmentContext,
|
|
347
|
-
resolvedWorkspace:
|
|
351
|
+
resolvedWorkspace: buildTaskThreadScratchResolvedWorkspace(params.workspaceCollectionRootDir, params.channelId, 'cross-agent-independent-workspace-disabled'),
|
|
348
352
|
};
|
|
349
353
|
}
|
|
350
354
|
return {
|
|
351
355
|
taskAssignmentContext,
|
|
352
356
|
resolvedWorkspace: {
|
|
353
|
-
authority: 'task',
|
|
357
|
+
authority: 'task-execution-target',
|
|
354
358
|
taskId: task.id,
|
|
355
|
-
rootPath:
|
|
359
|
+
rootPath: executionLocalDirectory,
|
|
356
360
|
},
|
|
357
361
|
};
|
|
358
362
|
}
|
|
359
363
|
catch (error) {
|
|
360
364
|
return {
|
|
361
365
|
taskAssignmentContext,
|
|
362
|
-
resolvedWorkspace:
|
|
366
|
+
resolvedWorkspace: discussionOnlyWorkspace,
|
|
363
367
|
};
|
|
364
368
|
}
|
|
365
369
|
}
|
|
@@ -373,6 +377,12 @@ export function resolveChannelContextDirectory(stateRootDir, channelId) {
|
|
|
373
377
|
export function resolveChannelContextPayloadPath(stateRootDir, channelId) {
|
|
374
378
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_CONTEXT_PAYLOAD_FILENAME);
|
|
375
379
|
}
|
|
380
|
+
export function resolveTaskAssignmentStateDirectory(stateRootDir, channelId) {
|
|
381
|
+
return resolve(stateRootDir, TASK_ASSIGNMENT_STATE_ROOT_DIRNAME, encodeChannelPathSegment(channelId));
|
|
382
|
+
}
|
|
383
|
+
export function resolveTaskAssignmentStatePath(stateRootDir, channelId) {
|
|
384
|
+
return join(resolveTaskAssignmentStateDirectory(stateRootDir, channelId), TASK_ASSIGNMENT_STATE_FILENAME);
|
|
385
|
+
}
|
|
376
386
|
export function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir) {
|
|
377
387
|
return join(resolve(startupWorkspaceRootDir), TASK_ISOLATED_WORKSPACE_COLLECTION_DIRNAME);
|
|
378
388
|
}
|
|
@@ -397,6 +407,9 @@ export function resolveTaskIsolatedWorkspaceDirectory(workspaceCollectionRootDir
|
|
|
397
407
|
return join(resolveTaskWorkspaceRootDirectory(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId)), encodeChannelPathSegment(taskId), CHANNEL_WORKSPACE_DIRNAME);
|
|
398
408
|
}
|
|
399
409
|
export const resolveTaskWorkspaceDirectory = resolveTaskIsolatedWorkspaceDirectory;
|
|
410
|
+
export function resolveTaskThreadScratchWorkspaceDirectory(workspaceCollectionRootDir, channelId) {
|
|
411
|
+
return join(resolveChannelWorkspaceRootDirectory(workspaceCollectionRootDir, channelId), TASK_THREAD_SCRATCH_WORKSPACE_DIRNAME, CHANNEL_WORKSPACE_DIRNAME);
|
|
412
|
+
}
|
|
400
413
|
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
401
414
|
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
402
415
|
}
|
|
@@ -499,6 +512,8 @@ export class FileChannelContextStore {
|
|
|
499
512
|
workspaceCollectionRootDir;
|
|
500
513
|
taskReader;
|
|
501
514
|
taskResolutionTimeoutMs;
|
|
515
|
+
allowCrossAgentIndependentWorkspaceHandoff;
|
|
516
|
+
resolveStableAgentId;
|
|
502
517
|
constructor(stateRootDir, options = {}) {
|
|
503
518
|
this.stateRootDir = stateRootDir;
|
|
504
519
|
this.fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
|
|
@@ -508,6 +523,9 @@ export class FileChannelContextStore {
|
|
|
508
523
|
this.workspaceCollectionRootDir = resolveManagedWorkspaceCollectionRoot(options.workspaceCollectionRootDir, options.env, options.resolvedHomeDir);
|
|
509
524
|
this.taskReader = options.taskReader;
|
|
510
525
|
this.taskResolutionTimeoutMs = options.taskResolutionTimeoutMs ?? DEFAULT_TASK_WORKSPACE_RESOLUTION_TIMEOUT_MS;
|
|
526
|
+
this.allowCrossAgentIndependentWorkspaceHandoff
|
|
527
|
+
= options.allowCrossAgentIndependentWorkspaceHandoff ?? true;
|
|
528
|
+
this.resolveStableAgentId = options.resolveStableAgentId;
|
|
511
529
|
}
|
|
512
530
|
async prepare(inputOrChannelId, options) {
|
|
513
531
|
const input = typeof inputOrChannelId === 'string'
|
|
@@ -519,10 +537,10 @@ export class FileChannelContextStore {
|
|
|
519
537
|
const auxiliaryCollaborationEnabled = collaborationCommandsEnabled && collaborationRoutesAllowedForTurnMode;
|
|
520
538
|
const directoryPath = resolveChannelContextDirectory(this.stateRootDir, input.channelId);
|
|
521
539
|
const payloadPath = resolveChannelContextPayloadPath(this.stateRootDir, input.channelId);
|
|
540
|
+
const taskAssignmentStatePath = resolveTaskAssignmentStatePath(this.stateRootDir, input.channelId);
|
|
522
541
|
const claudeProjectedBriefPath = resolveClaudeProjectedBriefPathFromPayloadPath(payloadPath);
|
|
523
542
|
const gatewayCredentialPath = resolveGatewayCredentialPathFromPayloadPath(payloadPath);
|
|
524
|
-
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(
|
|
525
|
-
const existingResolvedWorkspace = await readExistingResolvedWorkspaceContext(payloadPath, this.fileSystem);
|
|
543
|
+
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(taskAssignmentStatePath, this.fileSystem);
|
|
526
544
|
const skillRuntime = this.skillRuntimeEnabled
|
|
527
545
|
? await this.resolveSkillRuntimeBestEffort()
|
|
528
546
|
: undefined;
|
|
@@ -548,34 +566,40 @@ export class FileChannelContextStore {
|
|
|
548
566
|
: {}),
|
|
549
567
|
}
|
|
550
568
|
: undefined;
|
|
551
|
-
const taskAssignmentContext =
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
569
|
+
const taskAssignmentContext = input.taskAssignmentContextOverride
|
|
570
|
+
?? buildTaskAssignmentContextForTurn({
|
|
571
|
+
incomingMessageType: input.incomingMessageType,
|
|
572
|
+
incomingContent: input.incomingContent,
|
|
573
|
+
}, existingTaskAssignmentContext);
|
|
555
574
|
const workspaceResolution = await resolveTaskThreadWorkspaceContext({
|
|
556
575
|
channelId: input.channelId,
|
|
557
576
|
workspaceCollectionRootDir: this.workspaceCollectionRootDir,
|
|
558
577
|
taskAssignmentContext,
|
|
559
|
-
existingResolvedWorkspace,
|
|
560
578
|
taskReader: this.taskReader,
|
|
561
579
|
taskResolutionTimeoutMs: this.taskResolutionTimeoutMs,
|
|
580
|
+
allowCrossAgentIndependentWorkspaceHandoff: this.allowCrossAgentIndependentWorkspaceHandoff,
|
|
581
|
+
resolveStableAgentId: this.resolveStableAgentId,
|
|
582
|
+
fileSystem: this.fileSystem,
|
|
562
583
|
});
|
|
563
584
|
const resolvedWorkspace = workspaceResolution.resolvedWorkspace;
|
|
585
|
+
const runtimeTaskAssignmentContext = workspaceResolution.taskAssignmentContext ?? taskAssignmentContext;
|
|
586
|
+
const persistedTaskAssignmentContext = input.taskAssignmentContextOverridePersistence === 'ephemeral'
|
|
587
|
+
? existingTaskAssignmentContext
|
|
588
|
+
: runtimeTaskAssignmentContext;
|
|
564
589
|
const runtimeSurface = buildRuntimeSurfaceForTurn({
|
|
565
590
|
localhostGateway,
|
|
566
591
|
collaboration: input.collaboration,
|
|
567
|
-
taskAssignmentContext:
|
|
592
|
+
taskAssignmentContext: runtimeTaskAssignmentContext,
|
|
568
593
|
});
|
|
569
|
-
const payload = buildChannelContextPayload(input.channelId, runtimeSurface, input.collaborationOutcome, input.attentionSnapshot, input.compactionSnapshot, input.taskThreadCollaborationContract, input.collaborationCapabilities, input.missedCollaborationDiagnostic, skillRuntime, localhostGateway,
|
|
594
|
+
const payload = buildChannelContextPayload(input.channelId, runtimeSurface, input.collaborationOutcome, input.attentionSnapshot, input.compactionSnapshot, input.taskThreadCollaborationContract, input.collaborationCapabilities, input.missedCollaborationDiagnostic, skillRuntime, localhostGateway, persistedTaskAssignmentContext || resolvedWorkspace
|
|
570
595
|
? {
|
|
571
|
-
...(
|
|
572
|
-
? { taskAssignmentContext:
|
|
596
|
+
...(persistedTaskAssignmentContext
|
|
597
|
+
? { taskAssignmentContext: persistedTaskAssignmentContext }
|
|
573
598
|
: {}),
|
|
574
599
|
resolvedWorkspace,
|
|
575
600
|
}
|
|
576
601
|
: undefined);
|
|
577
602
|
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
578
|
-
let resolvedWorkspaceMaterialized = false;
|
|
579
603
|
let claudeProjectedBriefWritten = false;
|
|
580
604
|
let payloadWritten = false;
|
|
581
605
|
let gatewayCredentialWritten = false;
|
|
@@ -601,14 +625,24 @@ export class FileChannelContextStore {
|
|
|
601
625
|
}
|
|
602
626
|
try {
|
|
603
627
|
await this.fileSystem.mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
604
|
-
await this.fileSystem.mkdir(
|
|
605
|
-
|
|
628
|
+
await this.fileSystem.mkdir(resolveTaskAssignmentStateDirectory(this.stateRootDir, input.channelId), { recursive: true, mode: 0o700 });
|
|
629
|
+
if (resolvedWorkspace.authority !== 'task-execution-target') {
|
|
630
|
+
await this.fileSystem.mkdir(resolvedWorkspace.rootPath, { recursive: true, mode: 0o700 });
|
|
631
|
+
}
|
|
606
632
|
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
607
633
|
if (!claudeProjectedBriefContent) {
|
|
608
634
|
await unlinkIfPresent(this.fileSystem, claudeProjectedBriefPath);
|
|
609
635
|
}
|
|
610
636
|
await this.fileSystem.writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
611
637
|
payloadWritten = true;
|
|
638
|
+
if (persistedTaskAssignmentContext) {
|
|
639
|
+
const stagingTaskAssignmentStatePath = join(resolveTaskAssignmentStateDirectory(this.stateRootDir, input.channelId), `.task-assignment-state.${randomUUID()}.json`);
|
|
640
|
+
await this.fileSystem.writeFile(stagingTaskAssignmentStatePath, `${JSON.stringify({ taskAssignmentContext: persistedTaskAssignmentContext }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
641
|
+
await this.fileSystem.rename(stagingTaskAssignmentStatePath, taskAssignmentStatePath);
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
await unlinkIfPresent(this.fileSystem, taskAssignmentStatePath);
|
|
645
|
+
}
|
|
612
646
|
if (gatewayCredential) {
|
|
613
647
|
// Replaced atomically: a CLI invocation left over from an earlier turn must read either
|
|
614
648
|
// the old credential or the new one, never a missing or half-written file, because the
|
|
@@ -639,14 +673,23 @@ export class FileChannelContextStore {
|
|
|
639
673
|
}
|
|
640
674
|
const partialContext = {
|
|
641
675
|
directoryPath,
|
|
642
|
-
payload
|
|
676
|
+
payload: {
|
|
677
|
+
...payload,
|
|
678
|
+
...(resolvedWorkspace.authority === 'task-execution-target' && runtimeTaskAssignmentContext?.active === true
|
|
679
|
+
? {
|
|
680
|
+
resolvedWorkspace: buildTaskThreadScratchResolvedWorkspace(this.workspaceCollectionRootDir, input.channelId),
|
|
681
|
+
}
|
|
682
|
+
: {}),
|
|
683
|
+
},
|
|
643
684
|
claudeProjectedBriefPath: claudeProjectedBriefWritten ? claudeProjectedBriefPath : undefined,
|
|
644
685
|
claudeProjectedBriefHash: claudeProjectedBriefWritten ? preparedContext.claudeProjectedBriefHash : undefined,
|
|
645
686
|
payloadPath: payloadWritten ? payloadPath : undefined,
|
|
646
687
|
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
647
688
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
648
689
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
649
|
-
resolvedWorkspace:
|
|
690
|
+
resolvedWorkspace: resolvedWorkspace.authority === 'task-execution-target' && runtimeTaskAssignmentContext?.active === true
|
|
691
|
+
? buildTaskThreadScratchResolvedWorkspace(this.workspaceCollectionRootDir, input.channelId)
|
|
692
|
+
: resolvedWorkspace,
|
|
650
693
|
};
|
|
651
694
|
throw new ChannelContextPreparationError('failed to persist channel context payload', partialContext, { cause: error });
|
|
652
695
|
}
|
package/dist/context/prompt.js
CHANGED
|
@@ -39,7 +39,9 @@ function buildLocalhostGatewayPromptLines(context) {
|
|
|
39
39
|
if (!context.skillRuntime) {
|
|
40
40
|
return lines;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
const taskThreadActive = context.taskAssignmentContext?.active === true
|
|
43
|
+
|| context.runtimeSurface?.task.currentThread === 'task-assignment-thread';
|
|
44
|
+
lines.push(taskThreadActive
|
|
43
45
|
? 'This turn runs inside a task assignment thread.'
|
|
44
46
|
: 'This turn runs in a parent channel, not a task thread.');
|
|
45
47
|
if (context.runtimeSurface?.task.currentThread === 'parent-channel'
|
|
@@ -61,7 +63,7 @@ function buildLocalhostGatewayPromptLines(context) {
|
|
|
61
63
|
const collaborationLive = context.localhostGateway.collaboration?.enabled === true
|
|
62
64
|
&& (context.collaborationTurnMode ?? 'ordinary') === 'ordinary';
|
|
63
65
|
if (!collaborationLive) {
|
|
64
|
-
lines.push('Auxiliary collaboration commands (
|
|
66
|
+
lines.push('Auxiliary collaboration commands (draft, send, mention) are not available this turn.');
|
|
65
67
|
}
|
|
66
68
|
else if (context.collaborationTurnExecutionId) {
|
|
67
69
|
lines.push(`Pass --turn-execution-id ${context.collaborationTurnExecutionId} on send, mention and draft.`);
|
|
@@ -77,13 +79,15 @@ function buildLocalhostGatewayPromptLines(context) {
|
|
|
77
79
|
function buildTaskAssignmentPromptLines(params) {
|
|
78
80
|
const isTaskAssignmentTurn = params.incomingMessageType?.trim() === 'task_assignment';
|
|
79
81
|
const taskAssignmentContext = params.promptContext?.taskAssignmentContext;
|
|
80
|
-
|
|
82
|
+
const taskAssignmentThreadActive = taskAssignmentContext?.active === true
|
|
83
|
+
|| params.promptContext?.runtimeSurface?.task.currentThread === 'task-assignment-thread';
|
|
84
|
+
if (!isTaskAssignmentTurn && !taskAssignmentThreadActive) {
|
|
81
85
|
return [];
|
|
82
86
|
}
|
|
83
87
|
if (params.provider === 'claude'
|
|
84
88
|
&& params.forceInlineForClaude !== true
|
|
85
89
|
&& !isTaskAssignmentTurn
|
|
86
|
-
&&
|
|
90
|
+
&& taskAssignmentThreadActive) {
|
|
87
91
|
return [];
|
|
88
92
|
}
|
|
89
93
|
const lines = isTaskAssignmentTurn
|
|
@@ -180,7 +184,7 @@ function buildClaudeProjectedBriefTurnDeltaLines(context) {
|
|
|
180
184
|
const collaborationLive = context.localhostGateway.collaboration?.enabled === true
|
|
181
185
|
&& (context.collaborationTurnMode ?? 'ordinary') === 'ordinary';
|
|
182
186
|
if (!collaborationLive) {
|
|
183
|
-
lines.push('Auxiliary collaboration commands (
|
|
187
|
+
lines.push('Auxiliary collaboration commands (draft, send, mention) are not available this turn.');
|
|
184
188
|
}
|
|
185
189
|
else if (context.collaborationTurnExecutionId) {
|
|
186
190
|
lines.push(`Pass --turn-execution-id ${context.collaborationTurnExecutionId} on send, mention and draft.`);
|
|
@@ -291,8 +295,8 @@ function buildTurnControlAttentionLines(context) {
|
|
|
291
295
|
const attentionLines = context?.attentionSnapshot
|
|
292
296
|
? [
|
|
293
297
|
'Attention controls are projection-only host hints. They do not bypass server-side mention policy or widen message delivery.',
|
|
294
|
-
`To follow ordinary delivered human messages in this channel, add "attentionUpdate":"follow-channel".`,
|
|
295
|
-
`To
|
|
298
|
+
`To pin a durable follow hint for ordinary delivered human messages in this channel, add "attentionUpdate":"follow-channel".`,
|
|
299
|
+
`To clear an explicit follow hint and return to server-policy wake for this channel, add "attentionUpdate":"unfollow-channel".`,
|
|
296
300
|
`To mute ordinary delivered human wake while keeping explicit mentions and task assignments untouched, add "attentionUpdate":"mute-channel".`,
|
|
297
301
|
`To set a durable channel-level attention hint, add "attentionUpdate":"claim-channel".`,
|
|
298
302
|
...(taskThreadAttentionAvailable
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { PreparedPromptContext, ProviderKind } from '../types.js';
|
|
2
2
|
export declare function buildResolvedWorkspaceGuidanceLines(context: PreparedPromptContext | undefined, provider?: ProviderKind): string[];
|
|
3
|
+
export declare function isDiscussionOnlyResolvedWorkspace(context: PreparedPromptContext | undefined): boolean;
|