Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
d57efd3
fix(den-api): build on Windows
pascalandr May 22, 2026
636c597
feat(desktop): support managed bootstrap state
pascalandr May 22, 2026
bdcc37f
feat(den): support static worker provisioner
pascalandr May 22, 2026
f7ebab2
feat(den): support Entra SSO auto-join
pascalandr May 22, 2026
1304e7d
docs(docker): clean up on-prem runbooks
pascalandr May 22, 2026
af01579
fix(desktop): update bootstrap dependency lockfile
pascalandr May 22, 2026
a33f24f
test(den): keep provisioner tests isolated
pascalandr May 22, 2026
9aef096
feat(den): secure static worker attach
pascalandr May 22, 2026
e1b5888
test(den): cover static attach security
pascalandr May 22, 2026
928a08f
fix(den): make demo seed script portable
pascalandr May 23, 2026
d28b45f
feat(den): sync managed providers to workers
pascalandr May 22, 2026
f592203
fix(server): restore managed provider auth apply
pascalandr May 23, 2026
542a3dc
fix(den): harden provider sync verification
pascalandr May 22, 2026
2860334
feat(den): add provider credential contract base
pascalandr May 23, 2026
9776240
fix(den): treat empty provider sync as applied
pascalandr May 23, 2026
c7ae20f
feat(den): handle OAuth provider credentials
pascalandr May 23, 2026
c53a9cf
fix: type worker organization context
pascalandr May 23, 2026
d91928e
feat(den): add OpenAI OAuth provider flow
pascalandr May 23, 2026
d4b14c4
feat(den): add provider credential contract base
pascalandr May 23, 2026
efdefad
feat(den): handle OAuth provider credentials
pascalandr May 23, 2026
32b39e7
feat(app): import OAuth-backed Den providers
pascalandr May 23, 2026
bf09ed3
fix(server): restore managed provider auth apply
pascalandr May 23, 2026
f7ce71c
fix: TASK-2026-05-26-017 sanitize managed provider model config
pascalandr May 27, 2026
2a26ba3
fix: TASK-2026-05-26-020 filter managed OAuth models
pascalandr May 27, 2026
3690f38
fix: TASK-2026-05-26-021 handle live provider shapes
pascalandr May 28, 2026
976b68c
feat(den): add OpenAI OAuth provider flow
pascalandr May 23, 2026
e5fcafa
fix: TASK-2026-05-26-014 sync managed providers to remote workers
pascalandr May 26, 2026
4dbe93f
fix: TASK-2026-05-26-020 guard managed model picker
pascalandr May 27, 2026
5f52352
merge: TASK-2026-05-26-023 resolve PR 1939 with upstream dev
pascalandr May 28, 2026
7238bb5
chore: resolve pr 1891 conflicts
pascalandr May 28, 2026
0fe5587
chore: resolve pr 1895 conflicts
pascalandr May 28, 2026
0391a6a
chore: resolve pr 1940 conflicts
pascalandr May 28, 2026
e1ba67b
chore: resolve pr 1941 conflicts
pascalandr May 28, 2026
596108c
fix: TASK-2026-05-26-024 surface managed provider connections
pascalandr May 28, 2026
396af65
fix: TASK-2026-05-26-024 apply managed provider auth
pascalandr May 28, 2026
4112a09
fix: TASK-2026-05-26-024 persist Den remote workspace metadata
pascalandr May 28, 2026
0a9862b
fix: TASK-2026-05-28-002 address browser MCP review
pascalandr May 28, 2026
bc58883
merge: TASK-2026-05-28-007 resolve PR 1894 conflicts
pascalandr May 28, 2026
9fba3bf
fix: TASK-2026-05-28-008 address PR 1939 reviews
pascalandr May 28, 2026
c5ae1c6
test: TASK-2026-05-28-008 cover PR 1941 reviews
pascalandr May 28, 2026
95fa681
docs(docker): simplify static deployment runbook
pascalandr Jun 4, 2026
1db5746
fix(desktop): support Den cloud handoff on custom hosts
pascalandr Jun 4, 2026
824c2e4
Merge remote-tracking branch 'upstream/dev' into pr/docker-onprem-run…
pascalandr Jun 4, 2026
ec990c0
Merge remote-tracking branch 'upstream/dev' into pr/static-provisione…
pascalandr Jun 4, 2026
f8c5570
Merge remote-tracking branch 'upstream/dev' into pr/den-api-windows-b…
pascalandr Jun 4, 2026
da26fff
Merge remote-tracking branch 'upstream/dev' into pr/managed-desktop-b…
pascalandr Jun 4, 2026
53a67e1
Merge remote-tracking branch 'upstream/dev' into pr/static-worker-att…
pascalandr Jun 4, 2026
af32033
fix(app): clean cloud worker merge marker
pascalandr Jun 4, 2026
f0df4fd
Merge remote-tracking branch 'upstream/dev' into pr/entra-sso-auto-join
pascalandr Jun 4, 2026
81f6673
Merge remote-tracking branch 'upstream/dev' into pr/den-oauth-provide…
pascalandr Jun 4, 2026
08aa051
Merge remote-tracking branch 'upstream/dev' into pr/credential-contra…
pascalandr Jun 4, 2026
c8d97ca
Merge remote-tracking branch 'upstream/dev' into pr/desktop-import-oa…
pascalandr Jun 4, 2026
33a1f4f
fix: TASK-2026-06-05-001 resolve managed desktop PR review
pascalandr Jun 5, 2026
3736a07
fix: TASK-2026-06-05-001 serialize static worker quota checks
pascalandr Jun 5, 2026
cf99a3c
fix: TASK-2026-06-05-001 cache docker dependency install
pascalandr Jun 5, 2026
c29208e
fix: TASK-2026-06-05-001 restore managed provider config writer
pascalandr Jun 5, 2026
fdd0a38
fix: TASK-2026-06-05-001 align oauth migration test
pascalandr Jun 5, 2026
09c4461
fix: TASK-2026-06-05-001 remove duplicate bootstrap normalizer
pascalandr Jun 5, 2026
f54b60f
Merge remote-tracking branch 'upstream/dev' into pr/den-oauth-provide…
pascalandr Jun 9, 2026
e7cfa82
Merge remote-tracking branch 'upstream/dev' into pr/desktop-import-oa…
pascalandr Jun 9, 2026
0938ba5
Merge remote-tracking branch 'upstream/dev' into pr/credential-contra…
pascalandr Jun 9, 2026
d34c194
Merge remote-tracking branch 'upstream/dev' into pr/docker-onprem-run…
pascalandr Jun 9, 2026
ed8a685
Merge remote-tracking branch 'upstream/dev' into pr/entra-sso-auto-join
pascalandr Jun 9, 2026
ae825dc
Merge remote-tracking branch 'upstream/dev' into pr/static-worker-att…
pascalandr Jun 9, 2026
a321efb
Merge remote-tracking branch 'upstream/dev' into pr/static-provisione…
pascalandr Jun 9, 2026
e5787a8
Merge remote-tracking branch 'upstream/dev' into pr/den-api-windows-b…
pascalandr Jun 9, 2026
fe3229d
Merge remote-tracking branch 'upstream/dev' into pr/managed-desktop-b…
pascalandr Jun 9, 2026
4e40dd3
fix(docker): make static deployment images build from source
pascalandr Jun 9, 2026
4334240
docs(runbook): clarify static deployment verification flow
pascalandr Jun 9, 2026
ffcccaa
fix(den): restore static signup worker provisioning
pascalandr Jun 9, 2026
370948e
fix(desktop): connect cloud onboarding to remote workers
pascalandr Jun 9, 2026
1bd73bf
docs(docker): expose static attach policy env
pascalandr Jun 9, 2026
b62fb90
fix(den): align generated desktop app version
pascalandr Jun 9, 2026
1c5a329
fix(docker): pin and verify Bun install
pascalandr Jun 9, 2026
0b904c6
fix(den): harden static signup worker responses
pascalandr Jun 9, 2026
54721ed
fix(docker): select Bun artifact by target arch
pascalandr Jun 9, 2026
8638399
fix: TASK-2026-06-09-002 harden static Docker provisioning
pascalandr Jun 9, 2026
8b35e83
fix: TASK-2026-06-09-002 repair invite and SSO membership flows
pascalandr Jun 9, 2026
07474f0
fix: TASK-2026-06-09-002 make managed provider sync authoritative
pascalandr Jun 9, 2026
aea30ac
fix: TASK-2026-06-09-002 scope host-token bootstrap access
pascalandr Jun 9, 2026
0c00087
fix: TASK-2026-06-09-002 guard LLM provider access lifecycle
pascalandr Jun 9, 2026
b1e1350
fix: TASK-2026-06-09-002 preserve cloud managed provider identity
pascalandr Jun 9, 2026
5514598
test: TASK-2026-06-09-002 cover browser page callbacks
pascalandr Jun 9, 2026
4a245ad
test: TASK-2026-06-09-002 align provider sync proxy auth
pascalandr Jun 9, 2026
f5bde5d
test: TASK-2026-06-09-002 typecheck browser callback test
pascalandr Jun 9, 2026
a76df15
fix: TASK-2026-06-09-002 type invitation member claim
pascalandr Jun 9, 2026
efcb923
fix: TASK-2026-06-09-002 keep host token off workspace discovery
pascalandr Jun 9, 2026
d802374
test: TASK-2026-06-09-002 cover invitation token lifecycle
pascalandr Jun 9, 2026
463df77
fix: TASK-2026-06-10-004 resolve PR 1891 dev conflicts
pascalandr Jun 10, 2026
09a1e4b
fix: TASK-2026-06-10-005 repair invitation member lifecycle
pascalandr Jun 10, 2026
fc10acb
fix: TASK-2026-06-10-005 defer stale auth deletion
pascalandr Jun 10, 2026
2ffcceb
fix: TASK-2026-06-10-005 show invited provider members
pascalandr Jun 10, 2026
79913b5
fix: TASK-2026-06-10-005 retry stale auth cleanup
pascalandr Jun 10, 2026
d9c2e07
fix: TASK-2026-06-10-005 parse invited provider access
pascalandr Jun 10, 2026
ffc4b41
Merge remote-tracking branch 'origin/pr/den-api-windows-build' into d…
pascalandr Jun 10, 2026
07d6f80
Merge remote-tracking branch 'origin/pr/managed-desktop-bootstrap' in…
pascalandr Jun 10, 2026
f0868bb
Merge remote-tracking branch 'origin/pr/static-provisioner-backend' i…
pascalandr Jun 10, 2026
02f7764
Merge remote-tracking branch 'origin/pr/static-worker-attach-security…
pascalandr Jun 10, 2026
bf4d0ae
Merge remote-tracking branch 'origin/pr/entra-sso-auto-join' into dev…
pascalandr Jun 10, 2026
ae231df
Merge remote-tracking branch 'origin/pr/docker-onprem-runbook' into d…
pascalandr Jun 10, 2026
3eef937
Merge remote-tracking branch 'origin/pr/credential-contract-managed-s…
pascalandr Jun 10, 2026
ff37526
Merge remote-tracking branch 'origin/pr/den-oauth-provider-flow' into…
pascalandr Jun 10, 2026
a9462c4
Merge remote-tracking branch 'origin/pr/desktop-import-oauth-den-prov…
pascalandr Jun 10, 2026
9eaa69e
test: TASK-2026-06-10-006 tolerate fetch response headers
pascalandr Jun 10, 2026
f0bddb4
Merge remote-tracking branch 'origin/pr/managed-desktop-bootstrap' in…
pascalandr Jun 10, 2026
9f980a8
fix: TASK-2026-06-10-006 preserve client-route token isolation
pascalandr Jun 10, 2026
606d2b9
fix: TASK-2026-06-10-006 preserve dev app APIs
pascalandr Jun 10, 2026
83307bb
fix: TASK-2026-06-10-008 harden Den OAuth handoff
pascalandr Jun 10, 2026
c4fcfbf
fix: TASK-2026-06-10-008 harden managed provider sync
pascalandr Jun 10, 2026
413cb0d
fix: TASK-2026-06-10-008 clean managed bootstrap workspace discovery
pascalandr Jun 10, 2026
3299a88
fix: TASK-2026-06-10-008 keep host tokens out of bearer auth
pascalandr Jun 10, 2026
8a0baf0
fix: TASK-2026-06-10-008 allow safe HTTPS static attach hosts
pascalandr Jun 10, 2026
6d1086b
docs: TASK-2026-06-10-008 document static worker token map
pascalandr Jun 10, 2026
c77d693
Merge remote-tracking branch 'origin/pr/den-api-windows-build' into d…
pascalandr Jun 10, 2026
5d8296f
Merge remote-tracking branch 'origin/pr/managed-desktop-bootstrap' in…
pascalandr Jun 10, 2026
fdba5bd
Merge remote-tracking branch 'origin/pr/static-provisioner-backend' i…
pascalandr Jun 10, 2026
efcf9fb
Merge remote-tracking branch 'origin/pr/static-worker-attach-security…
pascalandr Jun 10, 2026
bb847ac
Merge remote-tracking branch 'origin/pr/entra-sso-auto-join' into dev…
pascalandr Jun 10, 2026
cd59d76
Merge remote-tracking branch 'origin/pr/docker-onprem-runbook' into d…
pascalandr Jun 10, 2026
4663238
Merge remote-tracking branch 'origin/pr/credential-contract-managed-s…
pascalandr Jun 10, 2026
e9717fb
Merge remote-tracking branch 'origin/pr/den-oauth-provider-flow' into…
pascalandr Jun 10, 2026
81fcb74
Merge remote-tracking branch 'origin/pr/desktop-import-oauth-den-prov…
pascalandr Jun 10, 2026
2b9ae39
fix: TASK-2026-06-10-006 preserve client-route token isolation
pascalandr Jun 10, 2026
5f33f76
Merge remote-tracking branch 'origin/dev-0.15.4' into dev-0.15.4
pascalandr Jun 10, 2026
cb25538
fix: TASK-2026-06-10-008 reject duplicate managed provider runtime ids
pascalandr Jun 10, 2026
576cf60
fix: TASK-2026-06-10-008 filter managed Den runtime workspaces
pascalandr Jun 10, 2026
ab8dd7f
Merge branch 'pr/credential-contract-managed-sync' into dev-0.15.4
pascalandr Jun 10, 2026
1b9dd2b
Merge branch 'pr/managed-desktop-bootstrap' into dev-0.15.4
pascalandr Jun 10, 2026
9e1ee05
fix: TASK-2026-06-10-008 guard array provider models
pascalandr Jun 10, 2026
c18e514
merge: TASK-2026-06-10-008 update credential sync
pascalandr Jun 10, 2026
eeca524
fix: TASK-2026-06-10-008 filter array provider models
pascalandr Jun 10, 2026
b2f4616
merge: TASK-2026-06-10-008 update provider model filtering
pascalandr Jun 10, 2026
da92d43
fix: TASK-2026-06-10-008 correct invitation lifecycle hooks
pascalandr Jun 10, 2026
960fad7
Merge remote-tracking branch 'origin/pr/entra-sso-auto-join' into HEAD
pascalandr Jun 10, 2026
871e0b3
fix: TASK-2026-06-10-008 preserve remote workspace secrets
pascalandr Jun 10, 2026
540ca5e
fix: TASK-2026-06-10-008 keep pending provider access visible
pascalandr Jun 10, 2026
d859f52
fix: TASK-2026-06-10-008 ignore removed memberships
pascalandr Jun 10, 2026
de539a6
fix: TASK-2026-06-10-008 harden static attach and desktop fetch
pascalandr Jun 10, 2026
35cf5be
fix: TASK-2026-06-10-008 harden docker worker defaults
pascalandr Jun 10, 2026
f3397fe
Merge remote-tracking branch 'origin/pr/managed-desktop-bootstrap' in…
pascalandr Jun 10, 2026
07a5be0
Merge remote-tracking branch 'origin/pr/desktop-import-oauth-den-prov…
pascalandr Jun 10, 2026
27181a7
Merge remote-tracking branch 'origin/pr/entra-sso-auto-join' into HEAD
pascalandr Jun 10, 2026
449572d
Merge remote-tracking branch 'origin/pr/static-worker-attach-security…
pascalandr Jun 10, 2026
4e76f47
Merge remote-tracking branch 'origin/pr/docker-onprem-runbook' into HEAD
pascalandr Jun 10, 2026
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
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
.git
.github
.opencode
.codenomad
node_modules
**/node_modules
tmp
dist
**/dist
artifacts
apps/desktop/src-tauri/target
**/target
.env
.env.*
34 changes: 34 additions & 0 deletions apps/app/scripts/workspace-endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import { resolveWorkspaceEndpoint } from "../src/app/lib/workspace-endpoint";

describe("resolveWorkspaceEndpoint", () => {
test("does not use remote host token as bearer authorization", () => {
const endpoint = resolveWorkspaceEndpoint({
id: "rem_ws_123",
workspaceType: "remote",
baseUrl: "https://worker.example.test",
openworkHostUrl: "https://worker.example.test",
openworkToken: null,
openworkClientToken: null,
openworkHostToken: "host-token-must-not-be-bearer",
openworkWorkspaceId: "ws_123",
} as never, { baseUrl: "http://127.0.0.1:8791", token: "local-token" });

expect(endpoint?.token).toBe("");
});

test("uses remote client token before local server token", () => {
const endpoint = resolveWorkspaceEndpoint({
id: "rem_ws_123",
workspaceType: "remote",
baseUrl: "https://worker.example.test",
openworkHostUrl: "https://worker.example.test",
openworkToken: null,
openworkClientToken: "remote-client-token",
openworkHostToken: "host-token",
openworkWorkspaceId: "ws_123",
} as never, { baseUrl: "http://127.0.0.1:8791", token: "local-token" });

expect(endpoint?.token).toBe("remote-client-token");
});
});
63 changes: 63 additions & 0 deletions apps/app/src/app/cloud/managed-provider-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { CloudImportedProvider } from "./import-state";
import type { ModelOption, ProviderListItem } from "../types";

export function buildCloudManagedModelIdsByProvider(
importedCloudProviders: Record<string, CloudImportedProvider> | null | undefined,
): Map<string, Set<string>> {
const next = new Map<string, Set<string>>();
for (const imported of Object.values(importedCloudProviders ?? {})) {
const providerId = imported.providerId.trim();
if (!providerId) continue;
const modelIds = imported.modelIds.map((id) => id.trim()).filter(Boolean);
if (!modelIds.length) continue;
const merged = next.get(providerId) ?? new Set<string>();
for (const modelId of modelIds) merged.add(modelId);
next.set(providerId, merged);
}
return next;
}

export function isCloudManagedModelAllowed(
cloudManagedModelIdsByProvider: Map<string, Set<string>>,
providerId: string,
modelId: string,
) {
const allowedModelIds = cloudManagedModelIdsByProvider.get(providerId);
return !allowedModelIds || allowedModelIds.has(modelId);
}

export function hasCloudManagedModelAllowlist(
cloudManagedModelIdsByProvider: Map<string, Set<string>>,
providerId: string,
) {
return cloudManagedModelIdsByProvider.has(providerId);
}

export function buildCloudManagedModelOptions(input: {
providers: ProviderListItem[];
cloudManagedModelIdsByProvider: Map<string, Set<string>>;
isRecommendedProvider?: (providerId: string) => boolean;
}): ModelOption[] {
const options: ModelOption[] = [];
for (const provider of input.providers) {
const isCloudManaged = hasCloudManagedModelAllowlist(input.cloudManagedModelIdsByProvider, provider.id);
for (const [modelId, model] of Object.entries(provider.models)) {
if (!isCloudManagedModelAllowed(input.cloudManagedModelIdsByProvider, provider.id, modelId)) continue;
options.push({
providerID: provider.id,
modelID: modelId,
title: model.name || modelId,
description: provider.name,
behaviorTitle: "Reasoning",
behaviorLabel: "Default",
behaviorDescription: "",
behaviorValue: null,
isFree: false,
isConnected: true,
isRecommended: input.isRecommendedProvider?.(provider.id),
source: isCloudManaged || /^lpr_/i.test(provider.id) ? "cloud" : undefined,
});
}
}
return options;
}
160 changes: 116 additions & 44 deletions apps/app/src/app/lib/den.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ export type DenMcpToken = {
resource: string;
};

export type DenStaticWorkerAttachInput = {
name: string;
description?: string | null;
url: string;
clientToken: string;
hostToken: string;
activityToken?: string | null;
};

export type DenOrgLlmProviderModel = {
id: string;
name: string;
Expand All @@ -129,17 +138,29 @@ export type DenOrgLlmProviderModel = {
export type DenOrgLlmProvider = {
id: string;
source: "models_dev" | "custom" | "openwork";
credentialKind: "api_key" | "opencode_oauth";
providerId: string;
name: string;
providerConfig: Record<string, unknown>;
hasApiKey: boolean;
hasOpencodeAuth: boolean;
hasCredential: boolean;
models: DenOrgLlmProviderModel[];
createdAt: string | null;
updatedAt: string | null;
};

export type DenOrgLlmProviderConnection = DenOrgLlmProvider & {
apiKey: string | null;
opencodeAuth: string | null;
};

export type DenManagedProviderSyncResult = {
status: "applied" | "failed";
providerCount: number;
revision: string;
providerIds?: string[];
reason?: string;
};

export type DenPluginConfigObjectType = "skill" | "agent" | "command" | "tool" | "mcp" | "hook" | "context" | "custom";
Expand Down Expand Up @@ -590,8 +611,20 @@ function syncBootstrapSettingsToLocalStorage(config: DenBootstrapConfig) {
return;
}

const previousBaseUrl = window.localStorage.getItem(STORAGE_BASE_URL);
const previousOrigin = normalizeDenBaseUrl(previousBaseUrl) ?? "";
const nextOrigin = normalizeDenBaseUrl(config.baseUrl) ?? "";
const denOriginChanged = Boolean(previousOrigin && nextOrigin && previousOrigin !== nextOrigin);

window.localStorage.setItem(STORAGE_BASE_URL, config.baseUrl);
window.localStorage.setItem(STORAGE_API_BASE_URL, config.apiBaseUrl);

if (denOriginChanged) {
window.localStorage.removeItem(STORAGE_AUTH_TOKEN);
window.localStorage.removeItem(STORAGE_ACTIVE_ORG_ID);
window.localStorage.removeItem(STORAGE_ACTIVE_ORG_SLUG);
window.localStorage.removeItem(STORAGE_ACTIVE_ORG_NAME);
}
}

function getPendingBootstrapConfig(next: DenSettings): DenBootstrapConfig | null {
Expand Down Expand Up @@ -626,51 +659,18 @@ export async function initializeDenBootstrapConfig(): Promise<DenBootstrapConfig
return desktopBootstrapConfig;
}

// The shell IPC bridge can be momentarily unavailable at first paint;
// retry briefly before giving up so a boot race does not poison the
// session with build defaults.
const SHELL_BOOTSTRAP_ATTEMPTS = 3;
const SHELL_BOOTSTRAP_RETRY_DELAY_MS = 350;
for (let attempt = 1; attempt <= SHELL_BOOTSTRAP_ATTEMPTS; attempt += 1) {
try {
const bootstrap = await getDesktopBootstrapConfigFromShell() as ShellDesktopBootstrapConfig;
applyDesktopBootstrapConfig(resolveDenBootstrapConfig(bootstrap));
return desktopBootstrapConfig;
} catch (error) {
console.error("[den-bootstrap] shell read failed", attempt, error);
if (attempt < SHELL_BOOTSTRAP_ATTEMPTS) {
await new Promise((resolve) => setTimeout(resolve, SHELL_BOOTSTRAP_RETRY_DELAY_MS));
}
}
try {
const bootstrap = await getDesktopBootstrapConfigFromShell() as ShellDesktopBootstrapConfig;
applyDesktopBootstrapConfig(resolveDenBootstrapConfig(bootstrap));
} catch {
desktopBootstrapConfig = resolveDenBootstrapConfig({
baseUrl: BUILD_DEN_BASE_URL,
apiBaseUrl: BUILD_DEN_API_BASE_URL,
requireSignin: BUILD_DEN_REQUIRE_SIGNIN,
});
syncBootstrapSettingsToLocalStorage(desktopBootstrapConfig);
}

// All quick attempts failed. Keep build defaults in memory only — do NOT
// sync them to localStorage: previously synced values from a successful
// boot are more trustworthy than build defaults, and clobbering them
// silently reverted custom/self-hosted control planes to the production
// URL until a manual reload.
desktopBootstrapConfig = resolveDenBootstrapConfig({
baseUrl: BUILD_DEN_BASE_URL,
apiBaseUrl: BUILD_DEN_API_BASE_URL,
requireSignin: BUILD_DEN_REQUIRE_SIGNIN,
});

// Heal in the background without blocking boot: once the bridge comes up,
// apply the real shell config and notify listeners.
void (async () => {
for (let attempt = 0; attempt < 15; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 2_000));
try {
const bootstrap = await getDesktopBootstrapConfigFromShell() as ShellDesktopBootstrapConfig;
applyDesktopBootstrapConfig(resolveDenBootstrapConfig(bootstrap));
dispatchDenSettingsChanged({ settings: readDenSettings() });
return;
} catch {
// Bridge still unavailable — keep trying.
}
}
})();

return desktopBootstrapConfig;
}

Expand Down Expand Up @@ -1110,10 +1110,13 @@ function parseDenOrgLlmProvider(value: unknown): DenOrgLlmProvider | null {
return {
id: value.id,
source: value.source,
credentialKind: value.credentialKind === "opencode_oauth" ? "opencode_oauth" : "api_key",
providerId: value.providerId,
name: value.name,
providerConfig: parseJsonRecord(value.providerConfig),
hasApiKey: value.hasApiKey === true,
hasOpencodeAuth: value.hasOpencodeAuth === true,
hasCredential: value.hasCredential === true || value.hasApiKey === true || value.hasOpencodeAuth === true,
models: Array.isArray(value.models)
? value.models.flatMap((model) => {
const parsed = parseDenOrgLlmProviderModel(model);
Expand Down Expand Up @@ -1149,6 +1152,28 @@ function getDenOrgLlmProviderConnection(payload: unknown): DenOrgLlmProviderConn
return {
...provider,
apiKey: typeof payload.llmProvider.apiKey === "string" ? payload.llmProvider.apiKey : null,
opencodeAuth: typeof payload.llmProvider.opencodeAuth === "string" ? payload.llmProvider.opencodeAuth : null,
};
}

function getDenManagedProviderSyncResult(payload: unknown): DenManagedProviderSyncResult | null {
if (!isRecord(payload)) return null;
if (payload.status !== "applied" && payload.status !== "failed") return null;
if (typeof payload.providerCount !== "number" || !Number.isInteger(payload.providerCount) || payload.providerCount < 0) return null;
if (typeof payload.revision !== "string") return null;
const rawProviderIds = Array.isArray(payload.providerIds)
? payload.providerIds
: Array.isArray(payload.appliedProviderIds)
? payload.appliedProviderIds
: undefined;
const providerIds = rawProviderIds ? readStringArray(rawProviderIds) : undefined;
if (rawProviderIds && providerIds?.length !== payload.providerCount) return null;
return {
status: payload.status,
providerCount: payload.providerCount,
revision: payload.revision,
...(providerIds ? { providerIds } : {}),
...(typeof payload.reason === "string" ? { reason: payload.reason } : {}),
};
}

Expand Down Expand Up @@ -1914,6 +1939,32 @@ export function createDenClient(options: { baseUrl: string; apiBaseUrl?: string
return tokens;
},

async attachStaticWorker(orgId: string, input: DenStaticWorkerAttachInput): Promise<DenWorkerSummary> {
const payload = await requestJson<unknown>(baseUrls, "/v1/workers/static-attach", {
method: "POST",
token,
organizationId: orgId,
body: {
name: input.name,
description: input.description ?? undefined,
url: input.url,
clientToken: input.clientToken,
hostToken: input.hostToken,
activityToken: input.activityToken ?? undefined,
},
});
const workers = getWorkers({
workers: isRecord(payload) && isRecord(payload.worker)
? [{ ...payload.worker, instance: isRecord(payload.instance) ? payload.instance : null }]
: [],
});
const worker = workers[0];
if (!worker) {
throw new DenApiError(500, "invalid_worker_attach_payload", "Static worker attach response was missing worker details.");
}
return worker;
},

async listOrgSkills(orgId: string): Promise<DenOrgSkillCard[]> {
const payload = await requestJson<unknown>(baseUrls, "/v1/skills", {
method: "GET",
Expand Down Expand Up @@ -1987,7 +2038,7 @@ export function createDenClient(options: { baseUrl: string; apiBaseUrl?: string
async getOrgLlmProviderConnection(orgId: string, llmProviderId: string): Promise<DenOrgLlmProviderConnection> {
const payload = await requestJson<unknown>(
baseUrls,
`/v1/llm-providers/${encodeURIComponent(llmProviderId)}/connect`,
`/v1/llm-providers/${encodeURIComponent(llmProviderId)}/import-credential`,
{
method: "GET",
token,
Expand All @@ -2001,6 +2052,27 @@ export function createDenClient(options: { baseUrl: string; apiBaseUrl?: string
return provider;
},

async syncWorkerManagedProviders(orgId: string, workerId: string): Promise<DenManagedProviderSyncResult> {
const payload = await requestJson<unknown>(
baseUrls,
`/v1/workers/${encodeURIComponent(workerId)}/managed-providers/sync`,
{
method: "POST",
token,
organizationId: orgId,
body: {},
},
);
const result = getDenManagedProviderSyncResult(payload);
if (!result) {
throw new DenApiError(500, "invalid_managed_provider_sync_payload", "Managed provider sync response was invalid.");
}
if (result.status !== "applied") {
throw new DenApiError(502, "managed_provider_sync_failed", result.reason ?? "Managed provider sync failed.");
}
return result;
},

async listOrgMarketplaces(orgId: string): Promise<DenOrgMarketplace[]> {
const payload = await requestJson<unknown>(
baseUrls,
Expand Down
3 changes: 3 additions & 0 deletions apps/app/src/app/lib/desktop-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export type WorkspaceInfo = {
openworkHostToken?: string | null;
openworkWorkspaceId?: string | null;
openworkWorkspaceName?: string | null;
openworkDenBaseUrl?: string | null;
openworkDenOrgId?: string | null;
openworkDenWorkerId?: string | null;
sandboxBackend?: "docker" | "microsandbox" | null;
sandboxRunId?: string | null;
sandboxContainerName?: string | null;
Expand Down
Loading
Loading