-
Notifications
You must be signed in to change notification settings - Fork 127
feat(host-bash-proxy): resolve and validate target client; pass targetClientId through broadcast #29313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(host-bash-proxy): resolve and validate target client; pass targetClientId through broadcast #29313
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ interface PendingRequest { | |
| conversationId: string; | ||
| /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */ | ||
| detachAbort: () => void; | ||
| targetClientId?: string; | ||
| } | ||
|
|
||
| export class HostBashProxy { | ||
|
|
@@ -69,6 +70,7 @@ export class HostBashProxy { | |
| working_dir?: string; | ||
| timeout_seconds?: number; | ||
| env?: Record<string, string>; | ||
| targetClientId?: string; | ||
| }, | ||
| conversationId: string, | ||
| signal?: AbortSignal, | ||
|
|
@@ -78,6 +80,30 @@ export class HostBashProxy { | |
| return Promise.resolve(result); | ||
| } | ||
|
|
||
| const capableClients = assistantEventHub.listClientsByCapability("host_bash"); | ||
|
|
||
| let resolvedTargetClientId: string | undefined; | ||
|
|
||
| if (input.targetClientId) { | ||
| const target = assistantEventHub.getClientById(input.targetClientId); | ||
| if (!target || !target.capabilities.includes("host_bash")) { | ||
| return Promise.resolve({ | ||
| content: `Error: client "${input.targetClientId}" is not connected or does not support host_bash. Run \`assistant clients list --capability host_bash\` to see available clients.`, | ||
| isError: true, | ||
| }); | ||
| } | ||
| resolvedTargetClientId = input.targetClientId; | ||
| } else if (capableClients.length === 1) { | ||
| // Auto-resolve when exactly one capable client is connected. | ||
| resolvedTargetClientId = capableClients[0].clientId; | ||
| } else if (capableClients.length > 1) { | ||
| return Promise.resolve({ | ||
| content: `Error: multiple clients support host_bash (${capableClients.length} connected). Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_bash\` to see client IDs and labels.`, | ||
| isError: true, | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Returning an error whenever Useful? React with 👍 / 👎. |
||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| // capableClients.length === 0: fall through — existing timeout/error path handles it. | ||
|
|
||
| const requestId = uuid(); | ||
|
|
||
| return new Promise<ToolExecutionResult>((resolve, reject) => { | ||
|
|
@@ -98,10 +124,13 @@ export class HostBashProxy { | |
| { requestId, command: input.command }, | ||
| "Host bash proxy request timed out", | ||
| ); | ||
| const timeoutMessage = resolvedTargetClientId | ||
| ? `Host bash proxy timed out waiting for response from client ${resolvedTargetClientId}` | ||
| : "Host bash proxy timed out waiting for client response"; | ||
| resolve( | ||
| formatShellOutput( | ||
| "", | ||
| "Host bash proxy timed out waiting for client response", | ||
| timeoutMessage, | ||
| null, | ||
| true, | ||
| timeoutSec, | ||
|
|
@@ -139,20 +168,26 @@ export class HostBashProxy { | |
| timeoutSec, | ||
| conversationId, | ||
| detachAbort, | ||
| targetClientId: resolvedTargetClientId, | ||
| }); | ||
|
|
||
| try { | ||
| broadcastMessage({ | ||
| type: "host_bash_request", | ||
| requestId, | ||
| broadcastMessage( | ||
| { | ||
| type: "host_bash_request", | ||
| requestId, | ||
| conversationId, | ||
| command: input.command, | ||
| working_dir: input.working_dir, | ||
| timeout_seconds: input.timeout_seconds, | ||
| targetClientId: resolvedTargetClientId, | ||
| ...(input.env && Object.keys(input.env).length > 0 | ||
| ? { env: input.env } | ||
| : {}), | ||
| }, | ||
| conversationId, | ||
| command: input.command, | ||
| working_dir: input.working_dir, | ||
| timeout_seconds: input.timeout_seconds, | ||
| ...(input.env && Object.keys(input.env).length > 0 | ||
| ? { env: input.env } | ||
| : {}), | ||
| }); | ||
| { targetClientId: resolvedTargetClientId }, | ||
| ); | ||
| } catch (err) { | ||
| clearTimeout(timer); | ||
| this.pending.delete(requestId); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.