Skip to content
Draft
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
14 changes: 13 additions & 1 deletion apps/server-v2/src/adapters/remote-openwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ function normalizeBaseUrl(value: string) {
return value.replace(/\/+$/, "");
}

function sanitizeProxyResponse(response: Response) {
const headers = new Headers(response.headers);
headers.delete("content-encoding");
headers.delete("transfer-encoding");
headers.delete("content-length");
return new Response(response.body, {
headers,
status: response.status,
statusText: response.statusText,
});
}

function unwrapEnvelope<T>(payload: unknown): T {
if (payload && typeof payload === "object" && "ok" in (payload as Record<string, unknown>)) {
const record = payload as Record<string, unknown>;
Expand Down Expand Up @@ -154,5 +166,5 @@ export async function requestRemoteOpenworkRaw(input: {
throw new RouteError(502, "bad_gateway", text.trim() || `Remote OpenWork request failed with status ${response.status}.`);
}

return response;
return sanitizeProxyResponse(response);
}
239 changes: 239 additions & 0 deletions apps/server-v2/src/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ test("openapi route is generated from the live Hono app", async () => {
expect(document.paths["/system/opencode/health"].get.operationId).toBe("getSystemOpencodeHealth");
expect(document.paths["/system/runtime/versions"].get.operationId).toBe("getSystemRuntimeVersions");
expect(document.paths["/system/runtime/upgrade"].post.operationId).toBe("postSystemRuntimeUpgrade");
expect(document.paths["/v1/app-version"].get.operationId).toBe("getV1AppVersion");
expect(document.paths["/v1/workers"].get.operationId).toBe("getV1Workers");
expect(document.paths["/v1/workers/{workerId}/tokens"].post.operationId).toBe("postV1WorkersByWorkerIdTokens");
expect(document.paths["/v1/templates"].get.operationId).toBe("getV1Templates");
expect(document.paths["/v1/templates"].post.operationId).toBe("postV1Templates");
expect(document.paths["/v1/templates/{templateId}"].delete.operationId).toBe("deleteV1TemplatesByTemplateId");
expect(document.paths["/v1/skills"].get.operationId).toBe("getV1Skills");
expect(document.paths["/v1/skills"].post.operationId).toBe("postV1Skills");
expect(document.paths["/v1/skill-hubs"].get.operationId).toBe("getV1SkillHubs");
expect(document.paths["/v1/skill-hubs/{skillHubId}/skills"].post.operationId).toBe("postV1SkillHubsBySkillHubIdSkills");
expect(document.paths["/v1/llm-providers"].get.operationId).toBe("getV1LlmProviders");
expect(document.paths["/v1/llm-providers/{llmProviderId}/connect"].get.operationId).toBe("getV1LlmProvidersByLlmProviderIdConnect");
expect(document.paths["/system/cloud/bootstrap"].get.operationId).toBe("getSystemCloudBootstrap");
expect(document.paths["/dev/log"].get.operationId).toBe("getDevLog");
expect(document.paths["/dev/log"].post.operationId).toBe("postDevLog");
expect(document.paths["/v1/me"].get.operationId).toBe("getV1Me");
expect(document.paths["/v1/me/orgs"].get.operationId).toBe("getV1MeOrgs");
expect(document.paths["/v1/me/desktop-config"].get.operationId).toBe("getV1MeDesktopConfig");
expect(document.paths["/v1/auth/desktop-handoff/exchange"].post.operationId).toBe("postV1AuthDesktopHandoffExchange");
expect(document.paths["/api/auth/organization/set-active"].post.operationId).toBe("postApiAuthOrganizationSetActive");
expect(document.paths["/workspaces/{workspaceId}/cloud/llm-providers/state"].get.operationId).toBe("getWorkspacesByWorkspaceIdCloudLlmProvidersState");
expect(document.paths["/workspaces/{workspaceId}/cloud/llm-providers/sync"].post.operationId).toBe("postWorkspacesByWorkspaceIdCloudLlmProvidersSync");
expect(document.paths["/workspaces/{workspaceId}/cloud/llm-providers/{cloudProviderId}"].put.operationId).toBe("putWorkspacesByWorkspaceIdCloudLlmProvidersByCloudProviderId");
expect(document.paths["/workspaces/{workspaceId}/config/disabled-providers"].patch.operationId).toBe("patchWorkspacesByWorkspaceIdConfigDisabledProviders");
expect(document.paths["/system/servers/connect"].post.operationId).toBe("postSystemServersConnect");
expect(document.paths["/workspaces"].get.operationId).toBe("getWorkspaces");
expect(document.paths["/workspaces/local"].post.operationId).toBe("postWorkspacesLocal");
Expand All @@ -132,6 +156,221 @@ test("openapi route is generated from the live Hono app", async () => {
expect(document.paths["/workspaces/{workspaceId}/events"].get.operationId).toBe("getWorkspacesByWorkspaceIdEvents");
});

test("cloud compatibility routes proxy and persist cloud state through server-v2", async () => {
const originalFetch = globalThis.fetch;
const { app, dependencies } = createTestApp();

dependencies.services.managed.upsertCloudSignin({
auth: { authToken: "cloud-token" },
cloudBaseUrl: "https://app.openworklabs.com",
metadata: null,
orgId: null,
userId: null,
});

globalThis.fetch = (async (input, init) => {
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
const method = init?.method ?? (typeof input === "string" || input instanceof URL ? "GET" : input.method ?? "GET");
if (url.pathname === "/api/den/v1/app-version") {
return new Response(JSON.stringify({ latestAppVersion: "0.11.212", minAppVersion: "0.11.207" }), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/api/den/v1/me") {
return new Response(JSON.stringify({
user: { id: "usr_1", email: "omar@example.com", name: "Omar" },
session: { id: "ses_1" },
}), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/api/den/v1/me/orgs") {
return new Response(JSON.stringify({
orgs: [
{ id: "org_1", name: "Alpha", slug: "alpha", role: "owner", isActive: true },
{ id: "org_2", name: "Beta", slug: "beta", role: "member", isActive: false },
],
activeOrgId: "org_1",
activeOrgSlug: "alpha",
}), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/api/den/v1/me/desktop-config") {
return new Response(JSON.stringify({
allowedDesktopVersions: ["0.11.212"],
blockZenModel: true,
disallowNonCloudModels: true,
}), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/api/den/v1/workers") {
return new Response(JSON.stringify({
workers: [{ id: "worker_1", name: "Worker One", status: "ready", instance: { url: "https://worker.example.com", provider: "daytona" }, isMine: true, createdAt: "2026-04-23T00:00:00.000Z" }],
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/workers/worker_1/tokens") {
return new Response(JSON.stringify({
tokens: { client: "client-token", owner: "owner-token", host: "host-token" },
connect: { openworkUrl: "https://worker.example.com", workspaceId: "ws_remote_1" },
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/templates" && method === "GET") {
return new Response(JSON.stringify({
templates: [{ id: "tpl_1", organizationId: "org_1", name: "Starter", templateData: { preset: "starter" }, createdAt: null, updatedAt: null, creator: null }],
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/templates" && method === "POST") {
return new Response(JSON.stringify({
template: { id: "tpl_2", organizationId: "org_1", name: "Created", templateData: { preset: "new" }, createdAt: null, updatedAt: null, creator: null },
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/templates/tpl_2") {
return new Response(JSON.stringify({ ok: true }), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/skills" && method === "GET") {
return new Response(JSON.stringify({
skills: [{ id: "skill_1", title: "Org Skill", description: null, skillText: "hello", shared: "org", updatedAt: null }],
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/skills" && method === "POST") {
return new Response(JSON.stringify({ id: "skill_2" }), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/skill-hubs") {
return new Response(JSON.stringify({
skillHubs: [{ id: "hub_1", name: "Hub One", skills: [{ id: "skill_1", title: "Org Skill", description: null, skillText: "hello", shared: "org", updatedAt: null }] }],
}), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/den/v1/skill-hubs/hub_1/skills") {
return new Response(JSON.stringify({ ok: true }), { headers: { "Content-Type": "application/json" } });
}
if (url.pathname === "/api/auth/organization/set-active") {
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not found", { status: 404 });
}) as typeof fetch;

try {
const [versionResponse, meResponse, orgsResponse, desktopConfigResponse, workersResponse, workerTokensResponse, templatesResponse, skillsResponse, skillHubsResponse] = await Promise.all([
app.request("http://openwork.local/v1/app-version"),
app.request("http://openwork.local/v1/me"),
app.request("http://openwork.local/v1/me/orgs"),
app.request("http://openwork.local/v1/me/desktop-config"),
app.request("http://openwork.local/v1/workers?limit=20"),
app.request("http://openwork.local/v1/workers/worker_1/tokens", { method: "POST" }),
app.request("http://openwork.local/v1/templates"),
app.request("http://openwork.local/v1/skills"),
app.request("http://openwork.local/v1/skill-hubs"),
]);

expect(versionResponse.status).toBe(200);
expect(await versionResponse.json()).toMatchObject({ latestAppVersion: "0.11.212", minAppVersion: "0.11.207" });
expect(meResponse.status).toBe(200);
expect(await meResponse.json()).toMatchObject({ user: { id: "usr_1", email: "omar@example.com" } });
expect(orgsResponse.status).toBe(200);
expect(await orgsResponse.json()).toMatchObject({ activeOrgId: "org_1", activeOrgSlug: "alpha" });
expect(desktopConfigResponse.status).toBe(200);
expect(await desktopConfigResponse.json()).toMatchObject({ blockZenModel: true, disallowNonCloudModels: true });
expect(workersResponse.status).toBe(200);
expect(await workersResponse.json()).toMatchObject({ workers: [{ workerId: "worker_1", workerName: "Worker One" }] });
expect(workerTokensResponse.status).toBe(200);
expect(await workerTokensResponse.json()).toMatchObject({ tokens: { client: "client-token" } });
expect(templatesResponse.status).toBe(200);
expect(await templatesResponse.json()).toMatchObject({ templates: [{ id: "tpl_1", name: "Starter" }] });
expect(skillsResponse.status).toBe(200);
expect(await skillsResponse.json()).toMatchObject({ skills: [{ id: "skill_1", title: "Org Skill" }] });
expect(skillHubsResponse.status).toBe(200);
expect(await skillHubsResponse.json()).toMatchObject({ skillHubs: [{ id: "hub_1", name: "Hub One" }] });

const createTemplateResponse = await app.request("http://openwork.local/v1/templates", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Created", templateData: { preset: "new" } }),
});
expect(createTemplateResponse.status).toBe(200);
expect(await createTemplateResponse.json()).toMatchObject({ template: { id: "tpl_2", name: "Created" } });

const deleteTemplateResponse = await app.request("http://openwork.local/v1/templates/tpl_2", { method: "DELETE" });
expect(deleteTemplateResponse.status).toBe(200);
expect(await deleteTemplateResponse.json()).toMatchObject({ ok: true });

const createSkillResponse = await app.request("http://openwork.local/v1/skills", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ skillText: "hello", shared: "org" }),
});
expect(createSkillResponse.status).toBe(200);
expect(await createSkillResponse.json()).toMatchObject({ id: "skill_2" });

const addSkillToHubResponse = await app.request("http://openwork.local/v1/skill-hubs/hub_1/skills", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ skillId: "skill_2" }),
});
expect(addSkillToHubResponse.status).toBe(200);
expect(await addSkillToHubResponse.json()).toMatchObject({ ok: true });

const setActiveResponse = await app.request("http://openwork.local/api/auth/organization/set-active", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ organizationId: "org_2" }),
});

expect(setActiveResponse.status).toBe(200);
expect(await setActiveResponse.json()).toMatchObject({ ok: true, activeOrgId: "org_1", activeOrgSlug: "alpha" });
expect(dependencies.persistence.repositories.cloudSignin.getPrimary()?.metadata).toMatchObject({
activeOrgName: "Alpha",
activeOrgSlug: "alpha",
validatedUser: { id: "usr_1", email: "omar@example.com", name: "Omar" },
});
} finally {
globalThis.fetch = originalFetch;
}
});

test("cloud bootstrap and dev log routes expose the remaining compatibility surfaces", async () => {
const originalDevLog = process.env.OPENWORK_DEV_LOG_FILE;
const { app } = createTestApp();
const tempLogPath = `/tmp/openwork-server-v2-dev-log-${Math.random().toString(16).slice(2)}.jsonl`;
process.env.OPENWORK_DEV_LOG_FILE = tempLogPath;

try {
const bootstrapResponse = await app.request("http://openwork.local/system/cloud/bootstrap");
const bootstrapBody = await bootstrapResponse.json();
expect(bootstrapResponse.status).toBe(200);
expect(bootstrapBody.data).toMatchObject({
apiBaseUrl: expect.any(String),
baseUrl: expect.any(String),
requireSignin: false,
});

const probeResponse = await app.request("http://openwork.local/dev/log");
const probeBody = await probeResponse.json();
expect(probeResponse.status).toBe(200);
expect(probeBody).toMatchObject({ ok: true, path: tempLogPath });

const appendResponse = await app.request("http://openwork.local/dev/log", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify([{ scope: "test", value: 1 }]),
});
const appendBody = await appendResponse.json();
expect(appendResponse.status).toBe(200);
expect(appendBody).toMatchObject({ ok: true, count: 1 });
} finally {
if (originalDevLog === undefined) {
delete process.env.OPENWORK_DEV_LOG_FILE;
} else {
process.env.OPENWORK_DEV_LOG_FILE = originalDevLog;
}
}
});

test("runtime routes expose the initial server-owned status surfaces", async () => {
const { app } = createTestApp();

Expand Down
11 changes: 10 additions & 1 deletion apps/server-v2/src/context/app-dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createAuthService, type AuthService } from "../services/auth-service.js";
import { createCapabilitiesService, type CapabilitiesService } from "../services/capabilities-service.js";
import { createCloudService, type CloudService } from "../services/cloud-service.js";
import { createConfigMaterializationService, type ConfigMaterializationService } from "../services/config-materialization-service.js";
import { createManagedResourceService, type ManagedResourceService } from "../services/managed-resource-service.js";
import { createProcessInfoAdapter, type ProcessInfoAdapter } from "../adapters/process-info.js";
Expand All @@ -23,9 +24,10 @@ export type AppDependencies = {
environment: string;
persistence: ServerPersistence;
processInfo: ProcessInfoAdapter;
services: {
services: {
auth: AuthService;
capabilities: CapabilitiesService;
cloud: CloudService;
config: ConfigMaterializationService;
files: WorkspaceFileService;
managed: ManagedResourceService;
Expand Down Expand Up @@ -141,6 +143,12 @@ export function createAppDependencies(overrides: CreateAppDependenciesOverrides
serverId: persistence.registry.localServerId,
workingDirectory: persistence.workingDirectory,
});
const cloud = createCloudService({
config,
repositories: persistence.repositories,
serverId: persistence.registry.localServerId,
version,
});
const sessions = createWorkspaceSessionService({
repositories: persistence.repositories,
runtime,
Expand Down Expand Up @@ -179,6 +187,7 @@ export function createAppDependencies(overrides: CreateAppDependenciesOverrides
services: {
auth,
capabilities,
cloud,
config,
files,
managed,
Expand Down
29 changes: 29 additions & 0 deletions apps/server-v2/src/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ test("openapi generation writes the committed server-v2 contract", async () => {

const openApiContents = await Bun.file(path.join(packageDir, "openapi/openapi.json")).text();
expect(openApiContents).toContain('"/system/health"');
expect(openApiContents).toContain('"/v1/app-version"');
expect(openApiContents).toContain('"/v1/llm-providers"');
expect(openApiContents).toContain('"/v1/llm-providers/{llmProviderId}/connect"');
expect(openApiContents).toContain('"/system/cloud/bootstrap"');
expect(openApiContents).toContain('"/dev/log"');
expect(openApiContents).toContain('"/v1/me"');
expect(openApiContents).toContain('"/v1/me/orgs"');
expect(openApiContents).toContain('"/v1/me/desktop-config"');
expect(openApiContents).toContain('"/v1/auth/desktop-handoff/exchange"');
expect(openApiContents).toContain('"/api/auth/organization/set-active"');
expect(openApiContents).toContain('"/workspaces/{workspaceId}/cloud/llm-providers/state"');
expect(openApiContents).toContain('"/workspaces/{workspaceId}/cloud/llm-providers/sync"');
expect(openApiContents).toContain('"/workspaces/{workspaceId}/cloud/llm-providers/{cloudProviderId}"');
expect(openApiContents).toContain('"/workspaces/{workspaceId}/config/disabled-providers"');
expect(openApiContents).toContain('"getSystemHealth"');
expect(openApiContents).toContain('"/system/status"');
expect(openApiContents).toContain('"/system/cloud-signin"');
Expand All @@ -50,6 +64,21 @@ test("sdk generation succeeds from the server-v2 openapi document", async () =>

const sdkIndex = await Bun.file(path.join(repoDir, "packages/openwork-server-sdk/generated/index.ts")).text();
expect(sdkIndex).toContain("getSystemHealth");
expect(sdkIndex).toContain("getV1AppVersion");
expect(sdkIndex).toContain("getV1LlmProviders");
expect(sdkIndex).toContain("getV1LlmProvidersByLlmProviderIdConnect");
expect(sdkIndex).toContain("getSystemCloudBootstrap");
expect(sdkIndex).toContain("getDevLog");
expect(sdkIndex).toContain("postDevLog");
expect(sdkIndex).toContain("getV1Me");
expect(sdkIndex).toContain("getV1MeOrgs");
expect(sdkIndex).toContain("getV1MeDesktopConfig");
expect(sdkIndex).toContain("postV1AuthDesktopHandoffExchange");
expect(sdkIndex).toContain("postApiAuthOrganizationSetActive");
expect(sdkIndex).toContain("getWorkspacesByWorkspaceIdCloudLlmProvidersState");
expect(sdkIndex).toContain("postWorkspacesByWorkspaceIdCloudLlmProvidersSync");
expect(sdkIndex).toContain("putWorkspacesByWorkspaceIdCloudLlmProvidersByCloudProviderId");
expect(sdkIndex).toContain("patchWorkspacesByWorkspaceIdConfigDisabledProviders");
expect(sdkIndex).toContain("getSystemStatus");
expect(sdkIndex).toContain("getSystemCloudSignin");
expect(sdkIndex).toContain("getSystemManagedMcps");
Expand Down
Loading
Loading