Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions api/server/services/Files/Code/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,7 @@ async function readSandboxFile({
* @param {string} params.file_path
* @param {string} params.workspace_id
* @param {string} [params.workspace_instance_id]
* @param {boolean} [params.linked_worktrees]
* @param {number} params.start_line
* @param {number} params.max_lines
* @param {string} params.codeApiBaseUrl
Expand All @@ -1080,6 +1081,7 @@ async function readWorkspaceFile({
file_path,
workspace_id,
workspace_instance_id,
linked_worktrees,
start_line,
max_lines,
codeApiBaseUrl,
Expand All @@ -1093,6 +1095,7 @@ async function readWorkspaceFile({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down Expand Up @@ -1122,6 +1125,7 @@ async function readWorkspaceFile({
* @param {string} params.query
* @param {string} params.workspace_id
* @param {string} [params.workspace_instance_id]
* @param {boolean} [params.linked_worktrees]
* @param {string} [params.path]
* @param {number} params.max_results
* @param {string} params.codeApiBaseUrl
Expand All @@ -1134,6 +1138,7 @@ async function searchWorkspace({
query,
workspace_id,
workspace_instance_id,
linked_worktrees,
path,
max_results,
codeApiBaseUrl,
Expand All @@ -1147,6 +1152,7 @@ async function searchWorkspace({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down Expand Up @@ -1175,6 +1181,7 @@ async function searchWorkspace({
* @param {Object} params
* @param {string} params.workspace_id
* @param {string} [params.workspace_instance_id]
* @param {boolean} [params.linked_worktrees]
* @param {string} [params.path]
* @param {string} [params.after_path]
* @param {number} params.max_results
Expand All @@ -1187,6 +1194,7 @@ async function searchWorkspace({
async function listWorkspaceFiles({
workspace_id,
workspace_instance_id,
linked_worktrees,
path,
after_path,
max_results,
Expand All @@ -1201,6 +1209,7 @@ async function listWorkspaceFiles({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down Expand Up @@ -1230,6 +1239,7 @@ async function writeWorkspaceFile({
overwrite,
workspace_id,
workspace_instance_id,
linked_worktrees,
codeApiBaseUrl,
executionProfile,
bridgeWorkerId,
Expand All @@ -1241,6 +1251,7 @@ async function writeWorkspaceFile({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down Expand Up @@ -1270,6 +1281,7 @@ async function editWorkspaceFile({
expected_base_sha256,
workspace_id,
workspace_instance_id,
linked_worktrees,
codeApiBaseUrl,
executionProfile,
bridgeWorkerId,
Expand All @@ -1281,6 +1293,7 @@ async function editWorkspaceFile({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down Expand Up @@ -1309,6 +1322,7 @@ async function previewWorkspaceEdit({
edits,
workspace_id,
workspace_instance_id,
linked_worktrees,
codeApiBaseUrl,
executionProfile,
bridgeWorkerId,
Expand All @@ -1320,6 +1334,7 @@ async function previewWorkspaceEdit({
}) {
return executeWorkspaceTool({
baseURL: codeApiBaseUrl,
linkedWorktrees: linked_worktrees,
maxQueueWaitMs,
codeApiMaxRetryWaitMs: req?.config?.endpoints?.agents?.codeApiMaxRetryWaitMs,
maxRequestTimeoutMs,
Expand Down
2 changes: 2 additions & 0 deletions api/server/services/Files/Code/process.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2199,6 +2199,7 @@ describe('Code Process', () => {
await expect(
listWorkspaceFiles({
workspace_id: 'primary',
linked_worktrees: true,
path: 'src',
after_path: 'src/app.ts',
max_results: 20,
Expand Down Expand Up @@ -2226,6 +2227,7 @@ describe('Code Process', () => {
expect(getCodeApiAuthHeaders).toHaveBeenNthCalledWith(2, mockReq, 'worker-user-1');
expect(mockExecuteWorkspaceTool).toHaveBeenCalledWith({
baseURL: 'https://attached-code.example.com/v1',
linkedWorktrees: true,
authHeaders: expect.any(Function),
maxQueueWaitMs: 0,
codeApiMaxRetryWaitMs: undefined,
Expand Down
1 change: 1 addition & 0 deletions api/server/services/ToolService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2308,6 +2308,7 @@ async function loadToolsForExecution({
baseUrl: codeExecutionContext.baseUrl,
workspaceId: codeExecutionContext.codeWorkspace.workspaceId,
workspaceInstanceId: codeExecutionContext.codeWorkspace.workspaceInstanceId,
linkedWorktrees: codeExecutionContext.codeWorkspace.linkedWorktrees,
environment: codeExecutionContext.codeWorkspace.environment,
gitIdentity: agent?.git_identity,
maxTimeoutMs: resolveAttachedWorkspaceCommandTimeoutMax(
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/agents/__tests__/initialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,7 @@ describe('initializeAgent — execute_code capability expansion', () => {
workspaceId: 'project-a',
operations: ['read_file', 'list_files', 'execute_command'],
environment: { fingerprint: 'a'.repeat(64), repo: 'owner/project', actions: ['check'] },
linkedWorktrees: true,
},
};
if (protectedEdit) codeExecutionContext.codeWorkspace!.operations.push('edit_file');
Expand Down Expand Up @@ -3063,6 +3064,10 @@ describe('initializeAgent — execute_code capability expansion', () => {
(bashTool?.parameters as { properties?: { timeoutMs?: { maximum?: number } } })?.properties
?.timeoutMs?.maximum,
).toBe(120_000);
expect(
(bashTool?.parameters as { properties?: { cwd?: { description?: string } } })?.properties
?.cwd?.description,
).toContain('.worktrees/<name>');
},
);

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/agents/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export interface CodeExecutionContext {
codeWorkspace?: CodeWorkspaceSelection & {
operations: CodeWorkspaceOperation[];
workspaceInstanceId?: string;
/** The worker schedules each `.worktrees/<name>` of this root as its own lane. */
linkedWorktrees?: boolean;
/** Live Code API execution ceiling. Omitted by older deployments. */
maxCommandTimeoutMs?: number;
instructions?: CodeWorkspaceDescriptor['instructions'];
Expand Down
11 changes: 11 additions & 0 deletions packages/api/src/agents/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ export interface ToolExecuteOptions {
file_path: string;
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
start_line: number;
max_lines: number;
codeApiBaseUrl: string;
Expand All @@ -579,6 +580,7 @@ export interface ToolExecuteOptions {
query: string;
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
path?: string;
max_results: number;
codeApiBaseUrl: string;
Expand All @@ -594,6 +596,7 @@ export interface ToolExecuteOptions {
listWorkspaceFiles?: (params: {
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
path?: string;
after_path?: string;
max_results: number;
Expand All @@ -613,6 +616,7 @@ export interface ToolExecuteOptions {
overwrite: boolean;
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
codeApiBaseUrl: string;
executionProfile: CodeExecutionContext['executionProfile'];
bridgeWorkerId?: string;
Expand All @@ -628,6 +632,7 @@ export interface ToolExecuteOptions {
edits: Array<{ oldText: string; newText: string }>;
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
codeApiBaseUrl: string;
executionProfile: CodeExecutionContext['executionProfile'];
bridgeWorkerId?: string;
Expand All @@ -644,6 +649,7 @@ export interface ToolExecuteOptions {
expected_base_sha256?: string;
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
codeApiBaseUrl: string;
executionProfile: CodeExecutionContext['executionProfile'];
bridgeWorkerId?: string;
Expand Down Expand Up @@ -2474,6 +2480,7 @@ async function handleWorkspaceFileRead(
...(codeExecutionContext.codeWorkspace?.workspaceInstanceId
? { workspace_instance_id: codeExecutionContext.codeWorkspace.workspaceInstanceId }
: {}),
...(codeExecutionContext.codeWorkspace?.linkedWorktrees ? { linked_worktrees: true } : {}),
start_line: startLine,
max_lines: maxLines,
codeApiBaseUrl: codeExecutionContext.baseUrl,
Expand Down Expand Up @@ -2580,6 +2587,7 @@ async function handleWorkspaceSearchCall(
...(codeExecutionContext.codeWorkspace?.workspaceInstanceId
? { workspace_instance_id: codeExecutionContext.codeWorkspace.workspaceInstanceId }
: {}),
...(codeExecutionContext.codeWorkspace?.linkedWorktrees ? { linked_worktrees: true } : {}),
...(typeof args.path === 'string' && args.path.length > 0 ? { path: args.path } : {}),
max_results: Number(maxResults),
codeApiBaseUrl: codeExecutionContext.baseUrl,
Expand Down Expand Up @@ -2668,6 +2676,7 @@ async function handleWorkspaceListCall(
...(codeExecutionContext.codeWorkspace?.workspaceInstanceId
? { workspace_instance_id: codeExecutionContext.codeWorkspace.workspaceInstanceId }
: {}),
...(codeExecutionContext.codeWorkspace?.linkedWorktrees ? { linked_worktrees: true } : {}),
...(typeof args.path === 'string' && args.path.length > 0 ? { path: args.path } : {}),
...(typeof args.after_path === 'string' && args.after_path.length > 0
? { after_path: args.after_path }
Expand Down Expand Up @@ -3939,6 +3948,7 @@ function attachedWorkspaceMutationParams(
): {
workspace_id: string;
workspace_instance_id?: string;
linked_worktrees?: boolean;
codeApiBaseUrl: string;
executionProfile: CodeExecutionContext['executionProfile'];
bridgeWorkerId?: string;
Expand All @@ -3954,6 +3964,7 @@ function attachedWorkspaceMutationParams(
...(codeExecutionContext.codeWorkspace?.workspaceInstanceId
? { workspace_instance_id: codeExecutionContext.codeWorkspace.workspaceInstanceId }
: {}),
...(codeExecutionContext.codeWorkspace?.linkedWorktrees ? { linked_worktrees: true } : {}),
codeApiBaseUrl: codeExecutionContext.baseUrl,
...limits,
...(limits.maxRequestTimeoutMs == null
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/agents/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,7 @@ export async function initializeAgent(
workspaceOperations: attachedWorkspaceOperations,
workspaceCommandTimeoutMaxMs: attachedWorkspaceCommandTimeoutMaxMs,
workspaceEnvironment: trustedCodeExecutionContext.codeWorkspace?.environment,
workspaceLinkedWorktrees: trustedCodeExecutionContext.codeWorkspace?.linkedWorktrees,
});
toolDefinitions = codeExecResult.toolDefinitions;
recordCapabilityToolNames(AgentCapabilities.execute_code, codeExecResult.toolNames);
Expand Down Expand Up @@ -2325,6 +2326,7 @@ export async function initializeAgent(
userId: user?.id,
workspaceCommandTimeoutMaxMs: attachedWorkspaceCommandTimeoutMaxMs,
workspaceEnvironment: trustedCodeExecutionContext.codeWorkspace?.environment,
workspaceLinkedWorktrees: trustedCodeExecutionContext.codeWorkspace?.linkedWorktrees,
skillStates: params.skillStates,
defaultActiveOnShare: params.defaultActiveOnShare,
maxCatalogSkills: getMaxCatalogSkills(runtime),
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/agents/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ export interface InjectSkillCatalogParams {
/** Deployment ceiling advertised on attached Bash tool definitions. */
workspaceCommandTimeoutMaxMs?: number;
workspaceEnvironment?: CodeWorkspaceDescriptor['environment'];
/** The worker runs `.worktrees/<name>` in its own lane; advertise `cwd` routing to the model. */
workspaceLinkedWorktrees?: boolean;
/** Current user ID — used to determine skill ownership for active-state resolution. */
userId?: string;
/** Per-user skill overrides: `{ [skillId]: boolean }`. Missing entries use the default. */
Expand Down Expand Up @@ -677,6 +679,7 @@ export async function injectSkillCatalog(
workspaceOperations,
workspaceCommandTimeoutMaxMs,
workspaceEnvironment,
workspaceLinkedWorktrees,
userId,
skillStates,
defaultActiveOnShare = false,
Expand Down Expand Up @@ -863,6 +866,7 @@ export async function injectSkillCatalog(
workspaceOperations,
workspaceCommandTimeoutMaxMs,
workspaceEnvironment,
workspaceLinkedWorktrees,
});
workingDefs = codeExecResult.toolDefinitions;

Expand Down
18 changes: 18 additions & 0 deletions packages/api/src/agents/tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,24 @@ describe('registerCodeExecutionTools', () => {
});
expect(bash?.description).toContain('owner/app');
});

it('tells the model to route worktree commands through cwd only when lanes are available', () => {
const cwdDescription = (workspaceLinkedWorktrees: boolean): string | undefined => {
const bash = registerCodeExecutionTools({
toolRegistry: undefined,
toolDefinitions: [],
includeBash: true,
workspaceTools: true,
workspaceOperations: new Set(['execute_command']),
workspaceLinkedWorktrees,
}).toolDefinitions.find((def) => def.name === 'bash_tool');
return (bash?.parameters as { properties?: { cwd?: { description?: string } } })?.properties
?.cwd?.description;
};

expect(cwdDescription(true)).toContain('.worktrees/<name>');
expect(cwdDescription(false)).not.toContain('.worktrees');
});
const makeRegistry = (): LCToolRegistry => new Map() as unknown as LCToolRegistry;

describe('fresh run (no pre-existing defs or registry entries)', () => {
Expand Down
14 changes: 13 additions & 1 deletion packages/api/src/agents/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ export interface RegisterCodeExecutionToolsParams {
/** Deployment ceiling advertised on attached Bash tool definitions. */
workspaceCommandTimeoutMaxMs?: number;
workspaceEnvironment?: CodeWorkspaceDescriptor['environment'];
/** The worker runs `.worktrees/<name>` in its own lane; advertise `cwd` routing to the model. */
workspaceLinkedWorktrees?: boolean;
/**
* When `true`, the registered `bash_tool` description includes the
* LLM-facing `{{tool<idx>turn<turn>}}` reference syntax guide so the
Expand Down Expand Up @@ -1040,6 +1042,7 @@ function createBashToolDef(
workspaceTools = false,
workspaceCommandTimeoutMaxMs?: number,
workspaceEnvironment?: CodeWorkspaceDescriptor['environment'],
workspaceLinkedWorktrees = false,
): LCTool {
/* Passed as a variable (not an inline literal) so the extra
* `statefulSessions` key stays assignable against pinned SDK versions
Expand All @@ -1052,7 +1055,11 @@ function createBashToolDef(
? buildAttachedWorkspaceBashDescription(enableToolOutputReferences, workspaceEnvironment)
: buildBashExecutionToolDescription(descriptionOpts),
parameters: (workspaceTools
? buildAttachedWorkspaceBashSchema(workspaceCommandTimeoutMaxMs, workspaceEnvironment)
? buildAttachedWorkspaceBashSchema(
workspaceCommandTimeoutMaxMs,
workspaceEnvironment,
workspaceLinkedWorktrees,
)
: BashExecutionToolDefinition.schema) as unknown as LCTool['parameters'],
}) as LCTool;
}
Expand All @@ -1066,6 +1073,8 @@ function buildBashToolDef(opts: {
workspaceTools?: boolean;
workspaceCommandTimeoutMaxMs?: number;
workspaceEnvironment?: CodeWorkspaceDescriptor['environment'];
/** The worker runs `.worktrees/<name>` in its own lane; advertise `cwd` routing to the model. */
workspaceLinkedWorktrees?: boolean;
}): LCTool {
/* Stateful defs are built on demand: the stateless pair covers the
* default path, and per-run construction is negligible next to init. */
Expand All @@ -1076,6 +1085,7 @@ function buildBashToolDef(opts: {
opts.workspaceTools === true,
opts.workspaceCommandTimeoutMaxMs,
opts.workspaceEnvironment,
opts.workspaceLinkedWorktrees === true,
);
}
return opts.enableToolOutputReferences
Expand Down Expand Up @@ -1109,6 +1119,7 @@ export function registerCodeExecutionTools(
workspaceOperations,
workspaceCommandTimeoutMaxMs,
workspaceEnvironment,
workspaceLinkedWorktrees,
enableToolOutputReferences = false,
statefulSessions = false,
} = params;
Expand All @@ -1129,6 +1140,7 @@ export function registerCodeExecutionTools(
workspaceTools,
workspaceCommandTimeoutMaxMs,
workspaceEnvironment,
workspaceLinkedWorktrees,
}),
);
}
Expand Down
Loading
Loading