diff --git a/packages/app/src/contexts/session-context.service-status.test.ts b/packages/app/src/contexts/session-context.service-status.test.ts
index 747934b66..6f77b1e21 100644
--- a/packages/app/src/contexts/session-context.service-status.test.ts
+++ b/packages/app/src/contexts/session-context.service-status.test.ts
@@ -28,7 +28,7 @@ const runningScript: WorkspaceScriptPayload = {
type: "service",
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
diff --git a/packages/app/src/hooks/use-sidebar-workspaces-list.test.ts b/packages/app/src/hooks/use-sidebar-workspaces-list.test.ts
index a00e24bf9..34d3cfb03 100644
--- a/packages/app/src/hooks/use-sidebar-workspaces-list.test.ts
+++ b/packages/app/src/hooks/use-sidebar-workspaces-list.test.ts
@@ -50,7 +50,7 @@ const runningScript: WorkspaceScriptPayload = {
type: "service",
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -61,7 +61,7 @@ const stoppedScript: WorkspaceScriptPayload = {
type: "service",
hostname: "main.api.localhost",
port: 3001,
- url: "http://main.api.localhost:6767",
+ proxyUrl: "http://main.api.localhost:6767",
lifecycle: "stopped",
health: null,
exitCode: null,
diff --git a/packages/app/src/screens/workspace/workspace-scripts-button.tsx b/packages/app/src/screens/workspace/workspace-scripts-button.tsx
index 1e3ca7943..852378495 100644
--- a/packages/app/src/screens/workspace/workspace-scripts-button.tsx
+++ b/packages/app/src/screens/workspace/workspace-scripts-button.tsx
@@ -5,6 +5,7 @@ import { ChevronDown, ExternalLink, Globe, LoaderCircle, Play, SquareTerminal }
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import type { WorkspaceDescriptor } from "@/stores/session-store";
import { useSessionStore } from "@/stores/session-store";
+import { useHostRuntimeSnapshot } from "@/runtime/host-runtime";
import {
DropdownMenu,
DropdownMenuContent,
@@ -12,6 +13,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { useToast } from "@/contexts/toast-context";
import { openExternalUrl } from "@/utils/open-external-url";
+import { resolveWorkspaceScriptLink } from "@/utils/workspace-script-links";
type Script = WorkspaceDescriptor["scripts"][number];
@@ -42,6 +44,7 @@ export function WorkspaceScriptsButton({
const { theme } = useUnistyles();
const toast = useToast();
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null);
+ const activeConnection = useHostRuntimeSnapshot(serverId)?.activeConnection ?? null;
const startScriptMutation = useMutation({
mutationFn: async (scriptName: string) => {
@@ -105,7 +108,8 @@ export function WorkspaceScriptsButton({
{scripts.map((script) => {
const isRunning = script.lifecycle === "running";
const isService = (script.type ?? "service") === "service";
- const isLinkable = isService && isRunning && !!script.url;
+ const serviceLink = resolveWorkspaceScriptLink({ script, activeConnection });
+ const isLinkable = isService && isRunning && !!serviceLink.openUrl;
const exitCode = script.exitCode ?? null;
let dotColor: string;
@@ -133,7 +137,9 @@ export function WorkspaceScriptsButton({
styles.scriptRow,
hovered && isLinkable && styles.scriptRowHovered,
]}
- onPress={isLinkable ? () => void openExternalUrl(script.url!) : undefined}
+ onPress={
+ isLinkable ? () => void openExternalUrl(serviceLink.openUrl!) : undefined
+ }
disabled={!isLinkable}
>
{({ hovered }) => (
@@ -156,9 +162,9 @@ export function WorkspaceScriptsButton({
>
{script.scriptName}
- {isService && isRunning && script.url ? (
+ {isService && isRunning && serviceLink.labelUrl ? (
- {script.url.replace(/^https?:\/\//, "")}
+ {serviceLink.labelUrl.replace(/^https?:\/\//, "")}
) : !isService && !isRunning && exitCode !== null && exitCode !== 0 ? (
diff --git a/packages/app/src/stores/session-store.test.ts b/packages/app/src/stores/session-store.test.ts
index 58f2f8341..d78557644 100644
--- a/packages/app/src/stores/session-store.test.ts
+++ b/packages/app/src/stores/session-store.test.ts
@@ -41,7 +41,7 @@ describe("normalizeWorkspaceDescriptor", () => {
type: "service" as const,
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running" as const,
health: "healthy" as const,
exitCode: null,
@@ -69,7 +69,7 @@ describe("normalizeWorkspaceDescriptor", () => {
type: "service",
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -118,7 +118,7 @@ describe("mergeWorkspaces", () => {
type: "service",
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -133,7 +133,7 @@ describe("mergeWorkspaces", () => {
type: "service",
hostname: "main.web.localhost",
port: 3000,
- url: "http://main.web.localhost:6767",
+ proxyUrl: "http://main.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
diff --git a/packages/app/src/utils/workspace-script-links.test.ts b/packages/app/src/utils/workspace-script-links.test.ts
new file mode 100644
index 000000000..6a235203e
--- /dev/null
+++ b/packages/app/src/utils/workspace-script-links.test.ts
@@ -0,0 +1,64 @@
+import { describe, expect, it } from "vitest";
+import type { WorkspaceScriptPayload } from "@server/shared/messages";
+import type { ActiveConnection } from "@/runtime/host-runtime";
+import { resolveWorkspaceScriptLink } from "./workspace-script-links";
+
+const runningService: WorkspaceScriptPayload = {
+ scriptName: "web",
+ type: "service",
+ hostname: "feature.web.localhost",
+ port: 3000,
+ proxyUrl: "http://feature.web.localhost:6767",
+ lifecycle: "running",
+ health: "healthy",
+ exitCode: null,
+};
+
+function resolveLink(activeConnection: ActiveConnection | null) {
+ return resolveWorkspaceScriptLink({
+ script: runningService,
+ activeConnection,
+ });
+}
+
+describe("resolveWorkspaceScriptLink", () => {
+ it("uses the local proxy URL for loopback TCP connections", () => {
+ expect(
+ resolveLink({ type: "directTcp", endpoint: "localhost:6767", display: "localhost:6767" }),
+ ).toEqual({
+ openUrl: "http://feature.web.localhost:6767",
+ labelUrl: "http://feature.web.localhost:6767",
+ });
+ });
+
+ it("uses the local proxy URL for socket and pipe connections", () => {
+ expect(
+ resolveLink({ type: "directSocket", endpoint: "/tmp/paseo.sock", display: "socket" }),
+ ).toEqual({
+ openUrl: "http://feature.web.localhost:6767",
+ labelUrl: "http://feature.web.localhost:6767",
+ });
+ });
+
+ it("degrades to daemon-host plus service port for direct network connections", () => {
+ expect(
+ resolveLink({
+ type: "directTcp",
+ endpoint: "mac-mini.tail123.ts.net:6767",
+ display: "mac-mini.tail123.ts.net:6767",
+ }),
+ ).toEqual({
+ openUrl: "http://mac-mini.tail123.ts.net:3000",
+ labelUrl: "http://mac-mini.tail123.ts.net:3000",
+ });
+ });
+
+ it("shows the local proxy URL but disables opening over relay", () => {
+ expect(
+ resolveLink({ type: "relay", endpoint: "relay.paseo.sh:443", display: "relay" }),
+ ).toEqual({
+ openUrl: null,
+ labelUrl: "http://feature.web.localhost:6767",
+ });
+ });
+});
diff --git a/packages/app/src/utils/workspace-script-links.ts b/packages/app/src/utils/workspace-script-links.ts
new file mode 100644
index 000000000..a903df173
--- /dev/null
+++ b/packages/app/src/utils/workspace-script-links.ts
@@ -0,0 +1,64 @@
+import { parseHostPort } from "@server/shared/daemon-endpoints";
+import type { WorkspaceScriptPayload } from "@server/shared/messages";
+import type { ActiveConnection } from "@/runtime/host-runtime";
+
+export type ResolvedWorkspaceScriptLink = {
+ openUrl: string | null;
+ labelUrl: string | null;
+};
+
+function isLoopbackHost(host: string): boolean {
+ const normalizedHost = host.trim().toLowerCase();
+ return normalizedHost === "localhost" || normalizedHost === "127.0.0.1" || normalizedHost === "::1";
+}
+
+function buildDirectServiceUrl(endpoint: string, port: number): string | null {
+ try {
+ const { host, isIpv6 } = parseHostPort(endpoint);
+ const base = isIpv6 ? `[${host}]` : host;
+ return `http://${base}:${port}`;
+ } catch {
+ return null;
+ }
+}
+
+export function resolveWorkspaceScriptLink(input: {
+ script: WorkspaceScriptPayload;
+ activeConnection: ActiveConnection | null;
+}): ResolvedWorkspaceScriptLink {
+ const { script, activeConnection } = input;
+ if (script.type !== "service" || script.lifecycle !== "running") {
+ return { openUrl: null, labelUrl: null };
+ }
+
+ if (!activeConnection) {
+ return { openUrl: null, labelUrl: script.proxyUrl };
+ }
+
+ if (activeConnection.type === "relay") {
+ return { openUrl: null, labelUrl: script.proxyUrl };
+ }
+
+ if (activeConnection.type === "directSocket" || activeConnection.type === "directPipe") {
+ return { openUrl: script.proxyUrl, labelUrl: script.proxyUrl };
+ }
+
+ try {
+ const { host } = parseHostPort(activeConnection.endpoint);
+ if (isLoopbackHost(host)) {
+ return { openUrl: script.proxyUrl, labelUrl: script.proxyUrl };
+ }
+ } catch {
+ return { openUrl: null, labelUrl: script.proxyUrl };
+ }
+
+ if (script.port === null) {
+ return { openUrl: null, labelUrl: script.proxyUrl };
+ }
+
+ const directUrl = buildDirectServiceUrl(activeConnection.endpoint, script.port);
+ return {
+ openUrl: directUrl,
+ labelUrl: directUrl ?? script.proxyUrl,
+ };
+}
diff --git a/packages/server/src/server/bootstrap.ts b/packages/server/src/server/bootstrap.ts
index f3f594860..06770fb68 100644
--- a/packages/server/src/server/bootstrap.ts
+++ b/packages/server/src/server/bootstrap.ts
@@ -708,6 +708,7 @@ export async function createPaseoDaemon(
scriptRuntimeStore,
handleBranchChange,
() => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
+ () => (boundListenTarget?.type === "tcp" ? boundListenTarget.host : null),
(hostname) => scriptHealthMonitor.getHealthForHostname(hostname),
);
diff --git a/packages/server/src/server/script-status-projection.test.ts b/packages/server/src/server/script-status-projection.test.ts
index f11eb3190..ad13a369c 100644
--- a/packages/server/src/server/script-status-projection.test.ts
+++ b/packages/server/src/server/script-status-projection.test.ts
@@ -82,7 +82,7 @@ describe("script-status-projection", () => {
type: "script",
hostname: "typecheck",
port: null,
- url: null,
+ proxyUrl: null,
lifecycle: "stopped",
health: null,
exitCode: 0,
@@ -92,7 +92,7 @@ describe("script-status-projection", () => {
type: "service",
hostname: "web.localhost",
port: 3000,
- url: "http://web.localhost:6767",
+ proxyUrl: "http://web.localhost:6767",
lifecycle: "stopped",
health: null,
exitCode: null,
@@ -144,7 +144,7 @@ describe("script-status-projection", () => {
type: "service",
hostname: "feature-card.web.localhost",
port: 4321,
- url: "http://feature-card.web.localhost:6767",
+ proxyUrl: "http://feature-card.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -195,7 +195,7 @@ describe("script-status-projection", () => {
type: "service",
hostname: "web.localhost",
port: 4321,
- url: "http://web.localhost:6767",
+ proxyUrl: "http://web.localhost:6767",
lifecycle: "running",
health: null,
exitCode: null,
@@ -239,7 +239,7 @@ describe("script-status-projection", () => {
type: "service",
hostname: "docs.localhost",
port: 3002,
- url: "http://docs.localhost:6767",
+ proxyUrl: "http://docs.localhost:6767",
lifecycle: "running",
health: null,
exitCode: null,
@@ -277,7 +277,7 @@ describe("script-status-projection", () => {
type: "script",
hostname: "typecheck",
port: null,
- url: null,
+ proxyUrl: null,
lifecycle: "running",
health: null,
exitCode: null,
@@ -342,7 +342,7 @@ describe("script-status-projection", () => {
type: "service",
hostname: "api.localhost",
port: 3001,
- url: "http://api.localhost:6767",
+ proxyUrl: "http://api.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -352,7 +352,7 @@ describe("script-status-projection", () => {
type: "script",
hostname: "typecheck",
port: null,
- url: null,
+ proxyUrl: null,
lifecycle: "stopped",
health: null,
exitCode: null,
diff --git a/packages/server/src/server/script-status-projection.ts b/packages/server/src/server/script-status-projection.ts
index ea9154816..60ce72a09 100644
--- a/packages/server/src/server/script-status-projection.ts
+++ b/packages/server/src/server/script-status-projection.ts
@@ -33,7 +33,7 @@ function resolveWorkspaceBranchName(workspaceDirectory: string): string | null {
return readGitCommand(workspaceDirectory, "git symbolic-ref --short HEAD");
}
-function toServiceUrl(hostname: string, daemonPort: number | null): string | null {
+function toServiceProxyUrl(hostname: string, daemonPort: number | null): string | null {
if (daemonPort === null) {
return null;
}
@@ -64,7 +64,7 @@ function createConfiguredPayload(input: {
type: input.type,
hostname,
port: input.type === "service" ? input.configuredPort : null,
- url: input.type === "service" ? toServiceUrl(hostname, input.daemonPort) : null,
+ proxyUrl: input.type === "service" ? toServiceProxyUrl(hostname, input.daemonPort) : null,
lifecycle: "stopped",
health: null,
exitCode: null,
@@ -117,9 +117,9 @@ export function buildWorkspaceScriptPayloads(
hostname:
type === "service" ? (routeEntry?.hostname ?? payload.hostname) : payload.scriptName,
port: type === "service" ? (routeEntry?.port ?? payload.port) : null,
- url:
+ proxyUrl:
type === "service"
- ? toServiceUrl(routeEntry?.hostname ?? payload.hostname, options.daemonPort)
+ ? toServiceProxyUrl(routeEntry?.hostname ?? payload.hostname, options.daemonPort)
: null,
lifecycle: runtimeEntry?.lifecycle ?? payload.lifecycle,
health:
@@ -146,7 +146,7 @@ export function buildWorkspaceScriptPayloads(
type,
hostname,
port: type === "service" ? (routeEntry?.port ?? null) : null,
- url: type === "service" ? toServiceUrl(hostname, options.daemonPort) : null,
+ proxyUrl: type === "service" ? toServiceProxyUrl(hostname, options.daemonPort) : null,
lifecycle: runtimeEntry.lifecycle,
health:
type === "service" && routeEntry
diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts
index bec490c74..73cf0d0d8 100644
--- a/packages/server/src/server/session.ts
+++ b/packages/server/src/server/session.ts
@@ -437,6 +437,7 @@ export type SessionOptions = {
newBranch: string | null,
) => void;
getDaemonTcpPort?: () => number | null;
+ getDaemonTcpHost?: () => string | null;
resolveScriptHealth?: (hostname: string) => ScriptHealthState | null;
voice?: {
voiceAgentMcpStdio?: VoiceMcpStdioConfig | null;
@@ -621,6 +622,7 @@ export class Session {
newBranch: string | null,
) => void;
private readonly getDaemonTcpPort: (() => number | null) | null;
+ private readonly getDaemonTcpHost: (() => string | null) | null;
private readonly resolveScriptHealth:
| ((hostname: string) => ScriptHealthState | null)
| null;
@@ -684,6 +686,7 @@ export class Session {
scriptRuntimeStore,
onBranchChanged,
getDaemonTcpPort,
+ getDaemonTcpHost,
resolveScriptHealth,
voice,
voiceBridge,
@@ -728,6 +731,7 @@ export class Session {
this.scriptRuntimeStore = scriptRuntimeStore ?? null;
this.onBranchChanged = onBranchChanged;
this.getDaemonTcpPort = getDaemonTcpPort ?? null;
+ this.getDaemonTcpHost = getDaemonTcpHost ?? null;
this.resolveScriptHealth = resolveScriptHealth ?? null;
if (this.terminalManager) {
this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) =>
@@ -6429,6 +6433,7 @@ export class Session {
branchName: readGitCommand(workspace.directory, "git symbolic-ref --short HEAD"),
scriptName: request.scriptName,
daemonPort: this.getDaemonTcpPort?.() ?? null,
+ daemonListenHost: this.getDaemonTcpHost?.() ?? null,
routeStore: this.scriptRouteStore,
runtimeStore: this.scriptRuntimeStore,
terminalManager: this.terminalManager,
diff --git a/packages/server/src/server/websocket-server.ts b/packages/server/src/server/websocket-server.ts
index 17c732ad4..73d06b9df 100644
--- a/packages/server/src/server/websocket-server.ts
+++ b/packages/server/src/server/websocket-server.ts
@@ -253,6 +253,7 @@ export class VoiceAssistantWebSocketServer {
private readonly scriptRouteStore: ScriptRouteStore | null;
private readonly scriptRuntimeStore: WorkspaceScriptRuntimeStore | null;
private readonly getDaemonTcpPort: (() => number | null) | null;
+ private readonly getDaemonTcpHost: (() => string | null) | null;
private readonly resolveScriptHealth:
| ((hostname: string) => ScriptHealthState | null)
| null;
@@ -334,6 +335,7 @@ export class VoiceAssistantWebSocketServer {
newBranch: string | null,
) => void,
getDaemonTcpPort?: () => number | null,
+ getDaemonTcpHost?: () => string | null,
resolveScriptHealth?: (hostname: string) => ScriptHealthState | null,
) {
this.logger = logger.child({ module: "websocket-server" });
@@ -385,6 +387,7 @@ export class VoiceAssistantWebSocketServer {
this.scriptRuntimeStore = scriptRuntimeStore ?? null;
this.onBranchChanged = onBranchChanged ?? null;
this.getDaemonTcpPort = getDaemonTcpPort ?? null;
+ this.getDaemonTcpHost = getDaemonTcpHost ?? null;
this.resolveScriptHealth = resolveScriptHealth ?? null;
this.serverCapabilities = buildServerCapabilities({
readiness: this.speech?.getReadiness() ?? null,
@@ -698,6 +701,7 @@ export class VoiceAssistantWebSocketServer {
scriptRuntimeStore: this.scriptRuntimeStore ?? undefined,
onBranchChanged: this.onBranchChanged ?? undefined,
getDaemonTcpPort: this.getDaemonTcpPort ?? undefined,
+ getDaemonTcpHost: this.getDaemonTcpHost ?? undefined,
resolveScriptHealth: this.resolveScriptHealth ?? undefined,
voice: {
...(this.voice ?? {}),
diff --git a/packages/server/src/server/worktree-bootstrap.test.ts b/packages/server/src/server/worktree-bootstrap.test.ts
index 5f5592ae2..aa26442cf 100644
--- a/packages/server/src/server/worktree-bootstrap.test.ts
+++ b/packages/server/src/server/worktree-bootstrap.test.ts
@@ -657,4 +657,45 @@ describe("runAsyncWorktreeBootstrap", () => {
exitCode: null,
});
});
+
+ it("binds services to the network when the daemon listens on a non-loopback host", async () => {
+ writeFileSync(
+ join(repoDir, "paseo.json"),
+ JSON.stringify({
+ scripts: {
+ web: {
+ type: "service",
+ command: "npm run dev",
+ },
+ },
+ }),
+ );
+ execSync("git add paseo.json", { cwd: repoDir, stdio: "pipe" });
+ execSync("git -c commit.gpgsign=false commit -m 'add remote service script config'", {
+ cwd: repoDir,
+ stdio: "pipe",
+ });
+
+ const routeStore = new ScriptRouteStore();
+ const runtimeStore = new WorkspaceScriptRuntimeStore();
+ const createTerminalCalls: Array<{ cwd: string; name?: string; env?: Record }> =
+ [];
+
+ await spawnWorktreeScripts({
+ repoRoot: repoDir,
+ workspaceId: repoDir,
+ branchName: "feature-remote-service",
+ daemonPort: 6767,
+ daemonListenHost: "100.64.0.20",
+ routeStore,
+ runtimeStore,
+ terminalManager: createStubTerminalManager(createTerminalCalls) as any,
+ });
+
+ expect(createTerminalCalls).toHaveLength(1);
+ expect(createTerminalCalls[0]?.env?.HOST).toBe("0.0.0.0");
+ expect(createTerminalCalls[0]?.env?.PASEO_SCRIPT_URL).toBe(
+ "http://feature-remote-service.web.localhost:6767",
+ );
+ });
});
diff --git a/packages/server/src/server/worktree-bootstrap.ts b/packages/server/src/server/worktree-bootstrap.ts
index 7a517d9db..a553717f1 100644
--- a/packages/server/src/server/worktree-bootstrap.ts
+++ b/packages/server/src/server/worktree-bootstrap.ts
@@ -753,6 +753,7 @@ type SpawnWorkspaceScriptOptions = {
branchName: string | null;
scriptName: string;
daemonPort?: number | null;
+ daemonListenHost?: string | null;
routeStore: ScriptRouteStore;
runtimeStore: WorkspaceScriptRuntimeStore;
terminalManager: TerminalManager;
@@ -760,6 +761,24 @@ type SpawnWorkspaceScriptOptions = {
onLifecycleChanged?: () => void;
};
+function isLoopbackListenHost(host: string | null | undefined): boolean {
+ if (!host) {
+ return true;
+ }
+
+ const normalizedHost = host.trim().toLowerCase();
+ return (
+ normalizedHost === "localhost" ||
+ normalizedHost === "127.0.0.1" ||
+ normalizedHost === "::1" ||
+ normalizedHost === "[::1]"
+ );
+}
+
+export function resolveServiceBindHost(daemonListenHost: string | null | undefined): string {
+ return isLoopbackListenHost(daemonListenHost) ? "127.0.0.1" : "0.0.0.0";
+}
+
export async function spawnWorkspaceScript(
options: SpawnWorkspaceScriptOptions,
): Promise {
@@ -769,6 +788,7 @@ export async function spawnWorkspaceScript(
branchName,
scriptName,
daemonPort,
+ daemonListenHost,
routeStore,
runtimeStore,
terminalManager,
@@ -805,7 +825,7 @@ export async function spawnWorkspaceScript(
const env = serviceScript
? {
PORT: String(port),
- HOST: "127.0.0.1",
+ HOST: resolveServiceBindHost(daemonListenHost),
...(daemonPort !== null && daemonPort !== undefined
? {
PASEO_SCRIPT_URL: `http://${hostname!}:${daemonPort}`,
@@ -898,6 +918,7 @@ export async function spawnWorktreeScripts(options: {
workspaceId: string;
branchName: string | null;
daemonPort?: number | null;
+ daemonListenHost?: string | null;
routeStore: ScriptRouteStore;
runtimeStore: WorkspaceScriptRuntimeStore;
terminalManager: TerminalManager;
diff --git a/packages/server/src/shared/messages.ts b/packages/server/src/shared/messages.ts
index 72234419d..4c728d669 100644
--- a/packages/server/src/shared/messages.ts
+++ b/packages/server/src/shared/messages.ts
@@ -1866,7 +1866,7 @@ export const WorkspaceScriptPayloadSchema = z.object({
type: z.enum(["script", "service"]).optional().default("service"),
hostname: z.string(),
port: z.number().int().positive().nullable(),
- url: z.string().nullable(),
+ proxyUrl: z.string().nullable().optional().default(null),
lifecycle: WorkspaceScriptLifecycleSchema,
health: WorkspaceScriptHealthSchema.nullable(),
exitCode: z.number().nullable().optional().default(null),
diff --git a/packages/server/src/shared/messages.workspaces.test.ts b/packages/server/src/shared/messages.workspaces.test.ts
index e84f78c65..6cedf4659 100644
--- a/packages/server/src/shared/messages.workspaces.test.ts
+++ b/packages/server/src/shared/messages.workspaces.test.ts
@@ -139,7 +139,7 @@ describe("workspace message schemas", () => {
scriptName: "web",
hostname: "web.localhost",
port: 3000,
- url: "http://web.localhost:6767",
+ proxyUrl: "http://web.localhost:6767",
lifecycle: "running",
health: "healthy",
},
@@ -158,7 +158,7 @@ describe("workspace message schemas", () => {
type: "service",
hostname: "web.localhost",
port: 3000,
- url: "http://web.localhost:6767",
+ proxyUrl: "http://web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
@@ -205,7 +205,7 @@ describe("workspace message schemas", () => {
scriptName: "web",
hostname: "web.localhost",
port: null,
- url: null,
+ proxyUrl: null,
lifecycle: "stopped",
health: null,
},