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
5 changes: 5 additions & 0 deletions assistant/src/daemon/conversation-surfaces.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Pre-existing: buildUserFacingLabel reads cancelLabel for deny action

At conversation-surfaces.ts:1724, the deny branch reads surfaceData?.cancelLabel instead of surfaceData?.denyLabel. This means the deny action's user-facing label falls back to the cancel button's label rather than a dedicated deny label. This is a pre-existing issue not introduced by this PR, but it's in adjacent unchanged code within the same function.

(Refers to lines 1724-1726)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,10 @@ export async function surfaceProxyResolver(
// Record the action and proxy to the connected desktop client
const reasoning =
typeof input.reasoning === "string" ? input.reasoning : undefined;
const targetClientId =
typeof input.target_client_id === "string" && input.target_client_id !== ""
? input.target_client_id
: undefined;
ctx.hostCuProxy.recordAction(toolName, input, reasoning);
return ctx.hostCuProxy.request(
toolName,
Expand All @@ -1788,6 +1792,7 @@ export async function surfaceProxyResolver(
ctx.hostCuProxy.stepCount,
reasoning,
signal,
targetClientId,
Comment on lines 1792 to +1795
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate computer_use target client before dispatch

Passing target_client_id straight into hostCuProxy.request without checking that the client is still connected and has host_cu capability causes a bad/stale ID to hang until proxy timeout instead of failing fast. This is newly introduced by threading target_client_id through CU actions; a typo or stale ID now adds repeated 60s delays per step in multi-client sessions.

Useful? React with 👍 / 👎.

);
}

Expand Down
40 changes: 40 additions & 0 deletions assistant/src/tools/computer-use/definitions.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing target_client_id on computer_use_observe — incomplete transformation across proxied CU tools

The PR adds target_client_id to every proxied computer-use tool definition in definitions.ts (click, type_text, key, scroll, drag, wait, open_app, run_applescript) but omits it from computer_use_observe. The observe tool IS proxied to the client — it falls through to the non-terminal branch in surfaceProxyResolver at assistant/src/daemon/conversation-surfaces.ts:1787-1796, where targetClientId is extracted from input.target_client_id and forwarded to ctx.hostCuProxy.request(). Without the property in the observe tool's schema (properties: {} at line 498), the LLM has no way to provide a target_client_id for observe calls, so in multi-client setups the observation request cannot be targeted to a specific client. Terminal tools (computer_use_done, computer_use_respond) correctly omit it since they resolve locally without a client round-trip.

(Refers to lines 496-499)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export const computerUseClickTool: Tool = {
description:
"Explanation of what you see and why you are clicking here",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["reasoning"],
},
Expand Down Expand Up @@ -99,6 +104,11 @@ export const computerUseTypeTextTool: Tool = {
type: "string",
description: "Explanation of what you are typing and why",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["text", "reasoning"],
},
Expand Down Expand Up @@ -136,6 +146,11 @@ export const computerUseKeyTool: Tool = {
type: "string",
description: "Explanation of why you are pressing this key",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["key", "reasoning"],
},
Expand Down Expand Up @@ -190,6 +205,11 @@ export const computerUseScrollTool: Tool = {
type: "string",
description: "Explanation of why you are scrolling",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["direction", "amount", "reasoning"],
},
Expand Down Expand Up @@ -250,6 +270,11 @@ export const computerUseDragTool: Tool = {
type: "string",
description: "Explanation of what you are dragging and why",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["reasoning"],
},
Expand Down Expand Up @@ -285,6 +310,11 @@ export const computerUseWaitTool: Tool = {
type: "string",
description: "Explanation of what you are waiting for",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["duration_ms", "reasoning"],
},
Expand Down Expand Up @@ -323,6 +353,11 @@ export const computerUseOpenAppTool: Tool = {
description:
"Explanation of why you need to open or switch to this app",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["app_name", "reasoning"],
},
Expand Down Expand Up @@ -360,6 +395,11 @@ export const computerUseRunAppleScriptTool: Tool = {
description:
"Explanation of what this script does and why AppleScript is better than UI interaction for this step",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
},
},
required: ["script", "reasoning"],
},
Expand Down
26 changes: 26 additions & 0 deletions assistant/src/tools/host-filesystem/edit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { supportsHostProxy } from "../../channels/types.js";
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
import { RiskLevel } from "../../permissions/types.js";
import type { ToolDefinition } from "../../providers/types.js";
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
import { formatEditDiff } from "../shared/filesystem/format-diff.js";
import { hostPolicy } from "../shared/filesystem/path-policy.js";
Expand Down Expand Up @@ -37,6 +39,11 @@ class HostFileEditTool implements Tool {
description:
"Replace all occurrences instead of requiring a unique match (default: false)",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to execute this on. Required when multiple clients support host_file; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_file`.",
},
},
required: ["path", "old_string", "new_string"],
},
Expand Down Expand Up @@ -84,6 +91,24 @@ class HostFileEditTool implements Tool {

const replaceAll = input.replace_all === true;

const targetClientId =
typeof input.target_client_id === "string" && input.target_client_id !== ""
? input.target_client_id
: undefined;

const transportInterface = context.transportInterface;
if (
targetClientId == null &&
transportInterface != null &&
!supportsHostProxy(transportInterface) &&
assistantEventHub.listClientsByCapability("host_file").length > 1
) {
return {
content: `Error: multiple clients support host_file. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_file\` to see client IDs and labels.`,
isError: true,
};
}

// Proxy to connected client for execution on the user's machine
// when a capable client is available (managed/cloud-hosted mode).
if (HostFileProxy.instance.isAvailable()) {
Expand All @@ -94,6 +119,7 @@ class HostFileEditTool implements Tool {
old_string: oldString as string,
new_string: newString as string,
replace_all: replaceAll,
targetClientId,
},
context.conversationId,
context.signal,
Expand Down
26 changes: 26 additions & 0 deletions assistant/src/tools/host-filesystem/read.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { extname } from "node:path";

import { supportsHostProxy } from "../../channels/types.js";
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
import { RiskLevel } from "../../permissions/types.js";
import type { ToolDefinition } from "../../providers/types.js";
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
import {
IMAGE_EXTENSIONS,
Expand Down Expand Up @@ -37,6 +39,11 @@ class HostFileReadTool implements Tool {
type: "number",
description: "Maximum number of lines to read",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to execute this on. Required when multiple clients support host_file; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_file`.",
},
},
required: ["path"],
},
Expand All @@ -55,6 +62,24 @@ class HostFileReadTool implements Tool {
};
}

const targetClientId =
typeof input.target_client_id === "string" && input.target_client_id !== ""
? input.target_client_id
: undefined;

const transportInterface = context.transportInterface;
if (
targetClientId == null &&
transportInterface != null &&
!supportsHostProxy(transportInterface) &&
assistantEventHub.listClientsByCapability("host_file").length > 1
) {
Comment on lines +72 to +76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject disconnected explicit host_file target before fallback

The new target_client_id path only guards the targetClientId == null case, so if a caller specifies a target that disconnects before execution, this code skips the ambiguity check and later falls through to local filesystem execution when HostFileProxy.instance.isAvailable() is false. That can run host_file_read/write/edit against the daemon/container filesystem instead of the intended host client, which is a correctness and safety regression introduced by adding explicit targeting.

Useful? React with 👍 / 👎.

return {
content: `Error: multiple clients support host_file. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_file\` to see client IDs and labels.`,
isError: true,
};
}

// Proxy to connected client for execution on the user's machine
// when a capable client is available (managed/cloud-hosted mode),
// including image reads that need the host filesystem view.
Expand All @@ -65,6 +90,7 @@ class HostFileReadTool implements Tool {
path: rawPath,
offset: typeof input.offset === "number" ? input.offset : undefined,
limit: typeof input.limit === "number" ? input.limit : undefined,
targetClientId,
},
context.conversationId,
context.signal,
Expand Down
26 changes: 26 additions & 0 deletions assistant/src/tools/host-filesystem/write.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { supportsHostProxy } from "../../channels/types.js";
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
import { RiskLevel } from "../../permissions/types.js";
import type { ToolDefinition } from "../../providers/types.js";
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
import { hostPolicy } from "../shared/filesystem/path-policy.js";
Expand Down Expand Up @@ -28,6 +30,11 @@ class HostFileWriteTool implements Tool {
type: "string",
description: "The content to write to the file",
},
target_client_id: {
type: "string",
description:
"ID of the specific client to execute this on. Required when multiple clients support host_file; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_file`.",
},
},
required: ["path", "content"],
},
Expand All @@ -54,6 +61,24 @@ class HostFileWriteTool implements Tool {
};
}

const targetClientId =
typeof input.target_client_id === "string" && input.target_client_id !== ""
? input.target_client_id
: undefined;

const transportInterface = context.transportInterface;
if (
targetClientId == null &&
transportInterface != null &&
!supportsHostProxy(transportInterface) &&
assistantEventHub.listClientsByCapability("host_file").length > 1
) {
return {
content: `Error: multiple clients support host_file. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_file\` to see client IDs and labels.`,
isError: true,
};
}

// Proxy to connected client for execution on the user's machine
// when a capable client is available (managed/cloud-hosted mode).
if (HostFileProxy.instance.isAvailable()) {
Expand All @@ -62,6 +87,7 @@ class HostFileWriteTool implements Tool {
operation: "write",
path: rawPath,
content: fileContent,
targetClientId,
},
context.conversationId,
context.signal,
Expand Down
Loading