Skip to content
Merged
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
12 changes: 7 additions & 5 deletions .docker/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,16 @@
ENV S3_ENDPOINT=http://minio:9000
ENV S3_PRESIGN_ENDPOINT=http://minio:9000
ENV S3_REGION=us-east-1
ENV S3_ACCESS_KEY_ID=roomote

Check warning on line 116 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "S3_ACCESS_KEY_ID") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV S3_SECRET_ACCESS_KEY=roomote-local-artifacts-password

Check warning on line 117 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "S3_SECRET_ACCESS_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV S3_BUCKET_ARTIFACTS=roomote-artifacts
ENV JOB_AUTH_PRIVATE_KEY=local-self-host-build-job-auth-private-key

Check warning on line 119 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "JOB_AUTH_PRIVATE_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV JOB_AUTH_PUBLIC_KEY=local-self-host-build-job-auth-public-key
ENV PREVIEW_AUTH_PRIVATE_KEY=local-self-host-build-preview-auth-private-key

Check warning on line 121 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "PREVIEW_AUTH_PRIVATE_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV PREVIEW_AUTH_PUBLIC_KEY=local-self-host-build-preview-auth-public-key
ENV DASHBOARD_PASSWORD=roomote-local-admin

Check warning on line 123 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "DASHBOARD_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV ENCRYPTION_KEY=local-roomote-encryption-key-0001

Check warning on line 124 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "ENCRYPTION_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV ARTIFACT_SIGNING_KEY=local-roomote-artifact-signing-key-1

Check warning on line 125 in .docker/app/Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build (app, amd64)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "ARTIFACT_SIGNING_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV PREVIEW_PROXY_BASE_URL=http://localhost:18081
ENV PREVIEW_DOMAINS=localhost,127.0.0.1,roomotepreview.localhost

Expand Down Expand Up @@ -178,12 +178,14 @@
# Runtime-only dependency tree for the externals tsup does not bundle. Install
# from its standalone lockfile so unrelated workspace packages stay excluded.
COPY .docker/app/runtime-deps/api /runtime-deps/
# The zod parity check guards the Fast agent's native tool runtime: it
# symlinks an on-disk zod into each generated OpenCode tool directory via
# require.resolve, which the api bundle (noExternal) cannot satisfy on its
# own. A version drifting from the workspace would silently change what the
# generated tool sources execute against.
# Version parity keeps API externals aligned with the workspace. Zod also
# guards the Fast agent's native tool runtime, which symlinks it into each
# generated OpenCode tool directory via require.resolve.
RUN cd /runtime-deps && pnpm install --prod --frozen-lockfile && \
test "$(node -p "require(require('node:path').join(require('node:path').dirname(require.resolve('dompurify')), '../package.json')).version")" = \
"$(cd /roomote/apps/api && node -p "require(require('node:path').join(require('node:path').dirname(require.resolve('dompurify')), '../package.json')).version")" && \
test "$(node -p "require('jsdom/package.json').version")" = \
"$(cd /roomote/apps/api && node -p "require('jsdom/package.json').version")" && \
test "$(node -p "require('snowflake-sdk/package.json').version")" = \
"$(cd /roomote/apps/api && node -p "require('snowflake-sdk/package.json').version")" && \
test "$(node -p "require('zod/package.json').version")" = \
Expand Down
2 changes: 2 additions & 0 deletions .docker/app/runtime-deps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"packageManager": "pnpm@10.29.3",
"dependencies": {
"dompurify": "3.4.13",
"jsdom": "26.1.0",
"snowflake-sdk": "2.4.3",
"zod": "3.25.76"
},
Expand Down
297 changes: 297 additions & 0 deletions .docker/app/runtime-deps/api/pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
"@sentry/node": "10.45.0",
"@trpc/client": "^11.15.0",
"@trpc/server": "^11.15.0",
"dompurify": "3.4.13",
"hono": "4.12.34",
"jose": "^6.2.3",
"jsdom": "26.1.0",
"p-map": "^7.0.4",
"snowflake-sdk": "^2.4.3",
"undici": "^7.29.0",
Expand Down
9 changes: 7 additions & 2 deletions apps/api/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ export default defineConfig({
js: `import { createRequire as __createRequire } from 'module';const require = __createRequire(import.meta.url);`,
},
esbuildOptions(options) {
// Exclude native modules and their runtime tree from bundling.
// Keep runtime-only dependency trees out of the API bundle.
// tsup-level `external` is ignored when `noExternal: [/.*/]` is enabled,
// so this must be applied at the esbuild layer.
options.external = [...(options.external ?? []), 'snowflake-sdk'];
options.external = [
...(options.external ?? []),
'dompurify',
'jsdom',
'snowflake-sdk',
];
},
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ function isRoomoteMcpServer(
return getMcpServerName(data) === ROOMOTE_MCP_SERVER_NAME;
}

function isTrustedRoomoteWidgetTool(
data: AcpToolCallUiMessage['data'] | AcpToolResultUiMessage['data'],
): boolean {
return (
(data.isMcp === true && isRoomoteMcpServer(data)) ||
(data.isMcp === false && data.isRoomoteNativeTool === true)
);
}

function clampWidgetHeight(height: unknown): number {
if (typeof height !== 'number' || !Number.isFinite(height)) {
return SHOW_WIDGET_DEFAULT_HEIGHT;
Expand Down Expand Up @@ -139,7 +148,7 @@ function isSettledToolResult(
export function resolveShowWidgetForToolMessage(
msg: AcpToolCallUiMessage | AcpToolResultUiMessage,
): ShowWidgetPayload | null {
if (msg.data.isMcp !== true || !isRoomoteMcpServer(msg.data)) {
if (!isTrustedRoomoteWidgetTool(msg.data)) {
return null;
}

Expand Down
3 changes: 0 additions & 3 deletions apps/worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@
"@trpc/server": "^11.15.0",
"chokidar": "^4.0.3",
"commander": "^14.0.2",
"dompurify": "3.4.13",
"execa": "9.6.1",
"hono": "4.12.34",
"http-proxy": "^1.18.1",
"ignore": "^7.0.5",
"jsdom": "26.1.0",
"jsonwebtoken": "^9.0.3",
"mime-types": "^2.1.35",
"node-pty": "^1.1.0",
Expand All @@ -53,7 +51,6 @@
"@roomote/config-eslint": "workspace:^",
"@roomote/config-typescript": "workspace:^",
"@types/http-proxy": "^1.17.17",
"@types/jsdom": "21.1.7",
"@types/jsonwebtoken": "^9.0.10",
"@types/mime-types": "^2.1.4",
"@types/node": "^24.10.13",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 11 additions & 10 deletions apps/worker/src/mcp/roomote-mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ import {
handleUpdateEnvironment,
} from './create-environment.js';
import { handleRequestEnvironmentVariables } from './request-environment-variables.js';
import { handleShowWidget } from './show-widget.js';
import {
handleShowWidget,
SHOW_WIDGET_FIXED_CANVAS_GUIDANCE,
SHOW_WIDGET_HEIGHT_DESCRIPTION,
SHOW_WIDGET_THEME_GUIDANCE,
} from './show-widget.js';
import { handleSendChatReply } from './send-chat-reply.js';
import { handleRelayFastAgentChatReply } from './relay-fast-agent-chat-reply.js';
import {
Expand Down Expand Up @@ -165,9 +170,10 @@ roomoteMcpServer.registerTool(
'Use it when a structured or visual presentation is clearer than plain text, or to demonstrate how something would look. ' +
'Examples include mock UI, status cards, tables, annotated plans, and other visual examples. ' +
'HTML, CSS, and inline SVG are displayed in a sandboxed iframe with scripts disabled and network requests blocked. ' +
'Prefer semantic HTML with the built-in widget classes (`rw-card`, `rw-stack`, `rw-row`, `rw-grid`, `rw-stat`, `rw-badge`, `rw-callout`, `rw-muted`) so the widget follows the host task theme. ' +
'For custom CSS, use the provided `--rw-*` theme variables instead of hard-coded colors; omit css when the built-in styles are sufficient. ' +
'Keep widgets compact enough to fit without scrolling: use concise labels and a small number of cards, rows, or table entries, and choose a height that fully fits the expected content. Use ordinary prose or an artifact for long content. ' +
SHOW_WIDGET_THEME_GUIDANCE +
' ' +
SHOW_WIDGET_FIXED_CANVAS_GUIDANCE +
' ' +
'Do not use it for ordinary prose or collecting user input; use request_user_input when you need answers. ' +
'Optional textFallback is delivered to the originating chat surface (Slack/Teams/Telegram/Discord) when the task was started from chat.',
inputSchema: {
Expand All @@ -184,12 +190,7 @@ roomoteMcpServer.registerTool(
.describe(
'Optional extra CSS injected after the built-in widget defaults. Prefer --rw-background, --rw-surface, --rw-surface-muted, --rw-text, --rw-text-muted, --rw-border, --rw-primary, --rw-accent, --rw-success, --rw-warning, and --rw-danger instead of hard-coded colors.',
),
height: z
.number()
.optional()
.describe(
'Optional widget iframe height in pixels (clamped to 120-800; default 320). Choose the smallest height that fully fits the expected content without a vertical scrollbar.',
),
height: z.number().optional().describe(SHOW_WIDGET_HEIGHT_DESCRIPTION),
textFallback: z
.string()
.optional()
Expand Down
Loading
Loading