From f32d44e10d93cbfad17c6a3db8eabd64ec4a0c7d Mon Sep 17 00:00:00 2001 From: Maksym Yezhov Date: Mon, 19 Oct 2026 22:03:22 -0700 Subject: [PATCH] chore: describe browser remote env properly for tangent --- .../TangentProjectAgentProvider.tsx | 6 ++- .../components/TangentRemoteEnvProvider.tsx | 6 ++- .../Tangent/services/remoteEnvHost.test.ts | 49 ++++++++++++++++++- .../pages/Tangent/services/remoteEnvHost.ts | 25 +++++++++- 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/routes/v2/pages/Tangent/components/TangentProjectAgentProvider.tsx b/src/routes/v2/pages/Tangent/components/TangentProjectAgentProvider.tsx index 224fd376bb..6b31f93753 100644 --- a/src/routes/v2/pages/Tangent/components/TangentProjectAgentProvider.tsx +++ b/src/routes/v2/pages/Tangent/components/TangentProjectAgentProvider.tsx @@ -22,7 +22,10 @@ import { type RunInspectDeps, } from "@/routes/v2/pages/Tangent/services/createWorkareaRemoteTools"; import { createRemoteEnvAgentWorker } from "@/routes/v2/pages/Tangent/services/remoteEnvAgentWorker"; -import { createRemoteEnvHost } from "@/routes/v2/pages/Tangent/services/remoteEnvHost"; +import { + createRemoteEnvHost, + describeBrowserEnv, +} from "@/routes/v2/pages/Tangent/services/remoteEnvHost"; import { createDebugBridgeHandlers } from "@/routes/v2/shared/components/AiChat/toolBridge/debugBridge"; import { createRunBridgeHandlers } from "@/routes/v2/shared/components/AiChat/toolBridge/runBridge"; import type { BridgeDeps } from "@/routes/v2/shared/components/AiChat/toolBridge/utils"; @@ -292,6 +295,7 @@ export function TangentProjectAgentProvider({ tools, sessionId: activeSessionId, agentTargets, + metadata: describeBrowserEnv({ mode: "editor" }), }); const stop = connectRemoteEnvWithRefresh({ diff --git a/src/routes/v2/pages/Tangent/components/TangentRemoteEnvProvider.tsx b/src/routes/v2/pages/Tangent/components/TangentRemoteEnvProvider.tsx index 331da8aead..ed9670ba03 100644 --- a/src/routes/v2/pages/Tangent/components/TangentRemoteEnvProvider.tsx +++ b/src/routes/v2/pages/Tangent/components/TangentRemoteEnvProvider.tsx @@ -26,7 +26,10 @@ import { useRemoteEnvAuthToken } from "@/routes/v2/pages/Tangent/hooks/useRemote import { useTangentBaseUrl } from "@/routes/v2/pages/Tangent/hooks/useTangentBaseUrl"; import { connectRemoteEnvWithRefresh } from "@/routes/v2/pages/Tangent/services/connectRemoteEnvWithRefresh"; import { createRemoteEnvAgentWorker } from "@/routes/v2/pages/Tangent/services/remoteEnvAgentWorker"; -import { createRemoteEnvHost } from "@/routes/v2/pages/Tangent/services/remoteEnvHost"; +import { + createRemoteEnvHost, + describeBrowserEnv, +} from "@/routes/v2/pages/Tangent/services/remoteEnvHost"; interface TangentRemoteEnvProviderProps { sessionId: string; @@ -135,6 +138,7 @@ export function TangentRemoteEnvProvider({ url: baseUrl, worker: remote, onError, + metadata: describeBrowserEnv(contextRef.current), }); const stop = connectRemoteEnvWithRefresh({ diff --git a/src/routes/v2/pages/Tangent/services/remoteEnvHost.test.ts b/src/routes/v2/pages/Tangent/services/remoteEnvHost.test.ts index ccdb747b98..684d01b04b 100644 --- a/src/routes/v2/pages/Tangent/services/remoteEnvHost.test.ts +++ b/src/routes/v2/pages/Tangent/services/remoteEnvHost.test.ts @@ -10,7 +10,7 @@ import type { RemoteEnvWorkerApi } from "@/agent/createRemoteEnvWorkerApi"; import type { ToolBridgeApi } from "@/agent/toolBridgeApi"; import type { AgentTargetRouter } from "./createActiveTabRoutingBridge"; -import { createRemoteEnvHost } from "./remoteEnvHost"; +import { createRemoteEnvHost, describeBrowserEnv } from "./remoteEnvHost"; const connectRemoteEnvironment = vi.fn(); @@ -139,6 +139,37 @@ describe("createRemoteEnvHost", () => { await expect(connection).resolves.toBeUndefined(); }); + it("forwards host metadata into the connect options", () => { + const worker = makeWorker(); + const metadata = { + editor: "browser", + description: "Tangent browser canvas", + }; + const host = createRemoteEnvHost({ + url: "http://localhost:8000", + worker: worker as unknown as Remote, + metadata, + }); + + void host.connect("token", "env-1"); + + const options = connectRemoteEnvironment.mock.calls.at(-1)?.[0]; + expect(options.metadata).toEqual(metadata); + }); + + it("omits metadata from the connect options when unset", () => { + const worker = makeWorker(); + const host = createRemoteEnvHost({ + url: "http://localhost:8000", + worker: worker as unknown as Remote, + }); + + void host.connect("token", "env-1"); + + const options = connectRemoteEnvironment.mock.calls.at(-1)?.[0]; + expect(options.metadata).toBeUndefined(); + }); + it("settles a connect that a disconnect superseded", async () => { const worker = makeWorker(); const host = createRemoteEnvHost({ @@ -594,3 +625,19 @@ describe("createRemoteEnvHost", () => { expect(onError).toHaveBeenCalledWith("kill failed"); }); }); + +describe("describeBrowserEnv", () => { + it("describes the editor host", () => { + const metadata = describeBrowserEnv({ mode: "editor" }); + expect(metadata.editor).toBe("browser"); + expect(metadata.description).toBe("Tangent browser canvas"); + expect(metadata.capabilities).toContain("edits the pipeline spec"); + }); + + it("describes the run-view host distinctly", () => { + const metadata = describeBrowserEnv({ mode: "runView", runId: "r1" }); + expect(metadata.editor).toBe("browser"); + expect(metadata.description).toBe("Tangent browser run inspector"); + expect(metadata.capabilities).toContain("read-only"); + }); +}); diff --git a/src/routes/v2/pages/Tangent/services/remoteEnvHost.ts b/src/routes/v2/pages/Tangent/services/remoteEnvHost.ts index 4c0bf68e7d..7813990af1 100644 --- a/src/routes/v2/pages/Tangent/services/remoteEnvHost.ts +++ b/src/routes/v2/pages/Tangent/services/remoteEnvHost.ts @@ -31,6 +31,7 @@ import type { Remote } from "comlink"; import { proxy } from "comlink"; import type { RemoteEnvWorkerApi } from "@/agent/createRemoteEnvWorkerApi"; +import type { AgentContext } from "@/agent/types"; import type { AgentTargetRouter } from "@/routes/v2/pages/Tangent/services/createActiveTabRoutingBridge"; import { getTangentSocketConfig } from "@/routes/v2/pages/Tangent/services/socketConfig"; import { isRecord } from "@/utils/typeGuards"; @@ -146,6 +147,26 @@ interface RemoteEnvHostBaseOptions { worker: Remote; onError?: (message: string) => void; agentTargets?: AgentTargetRouter; + metadata?: Record; +} + +// Self-description Prime reads via `list_environments` to tell this browser host +// apart from an editor host (e.g. the VS Code extension) and target it. +export function describeBrowserEnv( + context: AgentContext, +): Record { + if (context.mode === "runView") { + return { + editor: "browser", + description: "Tangent browser run inspector", + capabilities: "inspects a run's executions (read-only)", + }; + } + return { + editor: "browser", + description: "Tangent browser canvas", + capabilities: "edits the pipeline spec live on the browser canvas", + }; } interface RemoteEnvToolHostOptions { @@ -175,7 +196,8 @@ interface AgentLifecycle { export function createRemoteEnvHost( options: RemoteEnvHostOptions, ): RemoteEnvHost { - const { url, worker, onError, tools, sessionId, agentTargets } = options; + const { url, worker, onError, tools, sessionId, agentTargets, metadata } = + options; let client: RemoteEnvironmentClient | null = null; const agentLifecycles = new Map(); @@ -402,6 +424,7 @@ export function createRemoteEnvHost( environmentId, handlers, ...(loggedTools ? { tools: loggedTools, sessionId } : {}), + ...(metadata ? { metadata } : {}), }); client = nextClient; socketHolder.socket = nextClient.socket;