Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eb9ce95
feat: add stateful UI artifact capabilities
reachjalil Jul 23, 2026
7db497d
feat: render configurable UI artifacts in chat
reachjalil Jul 23, 2026
2877cb4
test: prove the UI artifact lifecycle
reachjalil Jul 23, 2026
0766715
fix: expose UI artifacts to agent context
reachjalil Jul 23, 2026
080b0bb
fix: align the approval artifact lifecycle
reachjalil Jul 23, 2026
02a5100
fix: package UI artifact MCP in Den image
reachjalil Jul 23, 2026
c0839dd
feat(ui-artifacts): add composable opt-in artifacts
reachjalil Jul 23, 2026
0d3286a
fix(ui-artifacts): keep steering turn-scoped
reachjalil Jul 23, 2026
d29f1b8
Merge remote-tracking branch 'upstream/dev' into feature/ui-artifacts
reachjalil Jul 28, 2026
8d97071
Merge remote-tracking branch 'upstream/dev' into feature/ui-artifacts
reachjalil Jul 28, 2026
8da8a0e
feat(artifacts): add managed dynamic React runtime
reachjalil Jul 28, 2026
0082240
Merge branch 'feature/ui-artifacts' into feature/dynamic-artifact-run…
reachjalil Jul 28, 2026
de1fa99
docs(artifacts): define trusted workspace boundary
reachjalil Jul 28, 2026
377f6ec
fix(artifacts): preserve rebuild diagnostics
reachjalil Jul 28, 2026
6b634fc
test(artifacts): document complete proposal experience
reachjalil Jul 28, 2026
bc474f6
fix(artifacts): preserve local toggle preferences
reachjalil Jul 28, 2026
0c60401
proposal(artifacts): bound generated chat shapes
reachjalil Jul 29, 2026
1751979
Sync current dev into UI Artifacts proposal
reachjalil Jul 30, 2026
649a535
Sync current dev and UI Artifacts proposal into dynamic artifacts
reachjalil Jul 30, 2026
31a87ef
fix: repair UI Artifacts proposal lockfile
reachjalil Jul 30, 2026
320e36a
fix: reconcile dynamic artifacts with current dev checks
reachjalil Jul 30, 2026
51d1c2c
Sync refreshed UI Artifacts proposal dependency
reachjalil Jul 30, 2026
3c5a8e0
fix: make artifact runtime import-safe in component tests
reachjalil Jul 30, 2026
175fa4c
fix: keep UI Artifact preferences fail-closed without app context
reachjalil Jul 30, 2026
f806cad
Sync finalized UI Artifacts proposal dependency
reachjalil Jul 30, 2026
b5d06f4
fix: tolerate partial window stubs in artifact runtime tests
reachjalil Jul 30, 2026
720e1e8
test: isolate message-list SSR from leaked window stubs
reachjalil Jul 30, 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
1 change: 1 addition & 0 deletions .github/workflows/publish-ee-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ on:
- "packages/email/**"
- "packages/types/**"
- "packages/ui/**"
- "packages/ui-artifact-mcp/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "package.json"
Expand Down
3 changes: 3 additions & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"@ai-sdk/react": "^3.0.148",
"@base-ui/react": "^1.4.1",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.3.3",
"@codemirror/language": "^6.11.0",
"@codemirror/state": "^6.5.2",
Expand Down
27 changes: 27 additions & 0 deletions apps/app/src/app/lib/den.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import {
normalizeDesktopConfig,
type DesktopConfig as SharedDesktopConfig,
} from "@openwork/types/den/desktop-policies";
import {
uiArtifactPreferencesSchema,
type UiArtifactPreferences,
type UiArtifactPreferencesUpdate,
} from "@openwork/types/ui-artifact";

// Re-export the shared schema under the local alias so React consumers
// (e.g. the cloud domain's desktop-config provider) can import it alongside
Expand Down Expand Up @@ -2228,6 +2233,28 @@ export function createDenClient(options: { baseUrl: string; token?: string | nul
return normalizeDenDesktopConfig(payload);
},

async getUiArtifactPreferences(orgId: string): Promise<UiArtifactPreferences> {
const payload = await requestJson<unknown>(baseUrls, "/v1/me/ui-artifacts", {
method: "GET",
token,
organizationId: orgId,
});
return uiArtifactPreferencesSchema.parse(payload);
},

async updateUiArtifactPreferences(
orgId: string,
update: UiArtifactPreferencesUpdate,
): Promise<UiArtifactPreferences> {
const payload = await requestJson<unknown>(baseUrls, "/v1/me/ui-artifacts", {
method: "PUT",
token,
organizationId: orgId,
body: update,
});
return uiArtifactPreferencesSchema.parse(payload);
},

async getResourceSnapshot(orgId?: string | null): Promise<DenResourceSnapshot> {
const payload = await requestJson<unknown>(baseUrls, "/v1/resources", {
method: "GET",
Expand Down
154 changes: 154 additions & 0 deletions apps/app/src/app/lib/openwork-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ import {
type AgentContextDiagnosticsRequest,
} from "@openwork/types/agent-context-diagnostics";
import { normalizeBaseUrl } from "@openwork/types/url";
import type {
UiArtifactBuild,
UiArtifactBuildRequest,
UiArtifactAgentSkill,
UiArtifactInstanceState,
UiArtifactIntentRequest,
UiArtifactIntentResult,
UiArtifactPinnedBuild,
UiArtifactProjectFileUpdate,
UiArtifactProjectSnapshot,
UiArtifactProjectSummary,
UiArtifactProjectUpdate,
UiArtifactPublishReceipt,
UiArtifactPublishRequest,
UiArtifactSettings,
UiArtifactSettingsUpdate,
UiArtifactStateUpdate,
} from "@openwork/types/ui-artifact-project";
import {
AGENT_CONTEXT_DIAGNOSTICS_REQUEST_TIMEOUT_MS,
requestAgentContextDiagnosticsPayload,
Expand All @@ -17,6 +35,25 @@ import type { ExecResult, OpencodeConfigFile, WorkspaceInfo, WorkspaceList } fro
import type { DenOrgMarketplace, DenOrgPluginResolved, DenResourceSnapshot } from "./den-types";
import type { CloudImportedMarketplace, CloudImportedPlugin } from "../cloud/import-state";

export type {
UiArtifactBuild,
UiArtifactBuildRequest,
UiArtifactAgentSkill,
UiArtifactInstanceState,
UiArtifactIntentRequest,
UiArtifactIntentResult,
UiArtifactPinnedBuild,
UiArtifactProjectFileUpdate,
UiArtifactProjectSnapshot,
UiArtifactProjectSummary,
UiArtifactProjectUpdate,
UiArtifactPublishReceipt,
UiArtifactPublishRequest,
UiArtifactSettings,
UiArtifactSettingsUpdate,
UiArtifactStateUpdate,
} from "@openwork/types/ui-artifact-project";

export type OpenworkServerCapabilities = {
skills: { read: boolean; write: boolean; source: "openwork" | "opencode" };
plugins: { read: boolean; write: boolean };
Expand Down Expand Up @@ -2023,6 +2060,123 @@ export function createOpenworkServerClient(options: { baseUrl: string; token?: s
{ token, hostToken, timeoutMs: timeouts.binary },
),

listUiArtifactProjects: (workspaceId: string) =>
requestJson<{ items: UiArtifactProjectSummary[] }>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts`,
{ token, hostToken },
),

getUiArtifactSettings: (workspaceId: string) =>
requestJson<UiArtifactSettings>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/settings`,
{ token, hostToken },
),

updateUiArtifactSettings: (workspaceId: string, update: UiArtifactSettingsUpdate) =>
requestJson<UiArtifactSettings>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/settings`,
{ token, hostToken, method: "PUT", body: update },
),

getUiArtifactAgentSkill: (workspaceId: string) =>
requestJson<UiArtifactAgentSkill>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/agent-skill`,
{ token, hostToken },
),

getUiArtifactProject: (workspaceId: string, slug: string) =>
requestJson<UiArtifactProjectSnapshot>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}`,
{ token, hostToken },
),

putUiArtifactProject: (
workspaceId: string,
slug: string,
update: UiArtifactProjectUpdate,
) =>
requestJson<UiArtifactProjectSnapshot>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}`,
{ token, hostToken, method: "PUT", body: update, timeoutMs: timeouts.binary },
),

putUiArtifactProjectFile: (
workspaceId: string,
slug: string,
update: UiArtifactProjectFileUpdate,
) =>
requestJson<UiArtifactProjectSnapshot>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/files`,
{ token, hostToken, method: "PUT", body: update },
),

buildUiArtifactProject: (
workspaceId: string,
slug: string,
request: UiArtifactBuildRequest = {},
) =>
requestJson<UiArtifactBuild>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/build`,
{ token, hostToken, method: "POST", body: request, timeoutMs: timeouts.binary },
),

publishUiArtifactProject: (
workspaceId: string,
slug: string,
request: UiArtifactPublishRequest = {},
) =>
requestJson<UiArtifactPublishReceipt>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/publish`,
{ token, hostToken, method: "POST", body: request, timeoutMs: timeouts.binary },
),

getUiArtifactBuild: (workspaceId: string, slug: string, projectRevision: string) =>
requestJson<UiArtifactPinnedBuild>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/builds/${encodeURIComponent(projectRevision)}`,
{ token, hostToken },
),

getUiArtifactInstanceState: (workspaceId: string, slug: string, instanceId: string) =>
requestJson<UiArtifactInstanceState>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/instances/${encodeURIComponent(instanceId)}/state`,
{ token, hostToken },
),

updateUiArtifactInstanceState: (
workspaceId: string,
slug: string,
instanceId: string,
update: UiArtifactStateUpdate,
) =>
requestJson<UiArtifactInstanceState>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/instances/${encodeURIComponent(instanceId)}/state`,
{ token, hostToken, method: "PUT", body: update },
),

stageUiArtifactIntent: (
workspaceId: string,
slug: string,
instanceId: string,
request: UiArtifactIntentRequest,
) =>
requestJson<UiArtifactIntentResult>(
baseUrl,
`/workspace/${encodeURIComponent(workspaceId)}/ui-artifacts/${encodeURIComponent(slug)}/instances/${encodeURIComponent(instanceId)}/intents`,
{ token, hostToken, method: "POST", body: request },
),

listArtifacts: (workspaceId: string) =>
requestJson<OpenworkArtifactList>(baseUrl, `/workspace/${encodeURIComponent(workspaceId)}/artifacts`, {
token,
Expand Down
11 changes: 11 additions & 0 deletions apps/app/src/components/chat/message-list-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use memo";

import { useSessionActivityStore } from "@/react-app/domains/session/status/session-activity-store"
import type { OpenworkServerClient } from "@/app/lib/openwork-server"
import type {
ChatToolReconnectAction,
ChatToolReconnectProgress,
Expand All @@ -9,6 +10,7 @@ import type {
import * as React from "react"

interface MessageListContextValue {
client: OpenworkServerClient
workspaceId: string
sessionId: string
showThinking: boolean
Expand All @@ -18,6 +20,7 @@ interface MessageListContextValue {
providerConnectedCount: number
dispatchAction: (action: DispatchAction) => void
setPrompt: (prompt: string) => void
stagePrompt: (prompt: string) => void
onRevertToUserMessage: (messageId: string) => void
onForkAtMessage: (messageId: string) => void
onEditUserMessage: (messageId: string, text: string) => void
Expand All @@ -33,6 +36,7 @@ const MessageListContext = React.createContext<MessageListContextValue | null>(n

interface MessageListProviderProps {
children: React.ReactNode
client: OpenworkServerClient
workspaceId: string
sessionId: string
showThinking: boolean
Expand All @@ -51,6 +55,7 @@ interface MessageListProviderProps {
providerConnectedCount: number
dispatchAction: (action: DispatchAction) => void
setPrompt: (prompt: string) => void
stagePrompt: (prompt: string) => void
}

export interface DispatchAction {
Expand All @@ -61,6 +66,7 @@ export interface DispatchAction {

export function MessageListProvider({
children,
client,
workspaceId,
sessionId,
showThinking,
Expand All @@ -70,6 +76,7 @@ export function MessageListProvider({
providerConnectedCount,
dispatchAction,
setPrompt,
stagePrompt,
onRevertToUserMessage,
onForkAtMessage,
onEditUserMessage,
Expand All @@ -79,6 +86,7 @@ export function MessageListProvider({
}: MessageListProviderProps) {
const value = React.useMemo(
() => ({
client,
workspaceId,
sessionId,
showThinking,
Expand All @@ -88,6 +96,7 @@ export function MessageListProvider({
providerConnectedCount,
dispatchAction,
setPrompt,
stagePrompt,
onRevertToUserMessage,
onForkAtMessage,
onEditUserMessage,
Expand All @@ -96,6 +105,7 @@ export function MessageListProvider({
onMcpRetry,
}),
[
client,
workspaceId,
sessionId,
showThinking,
Expand All @@ -105,6 +115,7 @@ export function MessageListProvider({
providerConnectedCount,
dispatchAction,
setPrompt,
stagePrompt,
onRevertToUserMessage,
onForkAtMessage,
onEditUserMessage,
Expand Down
Loading
Loading