diff --git a/.env.production.example b/.env.production.example index 991a1ea1b..b1354fd8f 100644 --- a/.env.production.example +++ b/.env.production.example @@ -74,6 +74,10 @@ OPENROUTER_API_KEY= # MOONSHOT_API_KEY= # KIMI_API_KEY= # MINIMAX_API_KEY= +# ZAI_API_KEY= +# ZAI_REGION=global +# ZAI_CODING_PLAN_API_KEY= +# ZAI_CODING_PLAN_REGION=global # OPENCODE_API_KEY= # Compute provider. Use docker for a single-host deployment, or diff --git a/.github/workflows/publish-ghcr.yml b/.github/workflows/publish-ghcr.yml index ae22e7739..da975ef65 100644 --- a/.github/workflows/publish-ghcr.yml +++ b/.github/workflows/publish-ghcr.yml @@ -469,7 +469,7 @@ jobs: name: Notify ops repository runs-on: ubuntu-latest needs: publish - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main') && vars.ROOMOTE_OPS_REPO != '' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.ROOMOTE_OPS_REPO != '' }} permissions: contents: read steps: @@ -480,20 +480,8 @@ jobs: run: | set -euo pipefail : "${GH_TOKEN:?ROOMOTE_OPS_DISPATCH_TOKEN is required}" - case "$GITHUB_REF" in - refs/heads/develop) - event_type=roomote-develop-build - image_tag="develop-${GITHUB_SHA:0:8}" - ;; - refs/heads/main) - event_type=roomote-main-build - image_tag="main-${GITHUB_SHA:0:8}" - ;; - *) - echo "Unexpected ref for notify-ops: $GITHUB_REF" >&2 - exit 1 - ;; - esac + event_type=roomote-main-build + image_tag="main-${GITHUB_SHA:0:8}" gh api "repos/${OPS_REPO}/dispatches" \ -f "event_type=${event_type}" \ -f "client_payload[image_tag]=${image_tag}" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index d79b20eba..103f1a094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ This file tracks product releases for Roomote (single monorepo version). Automated release entries are prepended by `pnpm run version`. +## 0.21.0 (2026-07-26) + +This release expands self-hosted deployment options and inference-provider choice, while making agent activity and automation more reliable and secure. + +### Highlights + +- Deploy self-hosted Roomote behind private networks and reverse tunnels with supported internal TLS. +- Add Z.AI and Z.AI Coding Plan as inference providers, with International and China region selection. Thanks to @pridemusvaire for this contribution. +- Keep task titles, routing, and summaries safely text-only when they process externally supplied input. +- Inspect the latest response from a subagent while it runs and after it completes. + +### Minor changes + +- Support internal TLS for self-hosted deployments behind reverse tunnels and private networks, without requiring public DNS or a custom Caddyfile. +- Add Z.AI and Z.AI Coding Plan as inference providers with International or China region on connect. + +### Patch changes + +- Restrict non-task OpenCode sessions to text-only output so task titles, routing, and summaries cannot act on instruction-like input. +- Keep Discord event handling and public-fork pull-request reviews reliable, while allowing self-hosted Docker environments to start on nftables-only hosts. +- Show the latest response from running and completed subagents, and give Dependabot automation clearer impact analysis and completion reporting. + ## 0.20.1 (2026-07-25) This release makes chat-driven automation more reliable, keeping Discord tasks moving and report-thread replies connected to their work. diff --git a/README.md b/README.md index 0bd15a3d4..3ee11cfde 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ cleans up after itself. 2. **API keys (BYOK).** Paste a key from OpenRouter, Anthropic, OpenAI, xAI, Google Gemini, Amazon Bedrock, Vercel AI Gateway, Baseten, Together AI, Moonshot AI (Kimi), Kimi for Coding, MiniMax, - OpenCode Zen / Go, or GitHub Copilot. + Z.AI (including Coding Plan), OpenCode Zen / Go, or GitHub Copilot. **Sandbox compute:** Modal, E2B, Daytona, Blaxel, and Local Docker. @@ -231,8 +231,8 @@ it runs. Two options. Connect your ChatGPT Plus or Pro subscription directly (no API key needed), or paste an API key from OpenRouter, Anthropic, OpenAI, xAI, Google Gemini, Amazon Bedrock, Vercel AI Gateway, Baseten, -Together AI, Moonshot AI (Kimi), Kimi for Coding, MiniMax, OpenCode Zen / Go, -or GitHub Copilot. +Together AI, Moonshot AI (Kimi), Kimi for Coding, MiniMax, Z.AI (including +Coding Plan), OpenCode Zen / Go, or GitHub Copilot. **What sandboxes does it support?** Modal, E2B, Daytona, Blaxel, and Local Docker. diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index 811f9b9f8..b9ddc8e9b 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -69,6 +69,26 @@ Point ``, `preview.`, and `*.preview.` A records at the server first; the installer waits briefly for DNS and Caddy retries certificates until the records are in place. +### Private networks and tunnels + +When a reverse tunnel or private network terminates public TLS before traffic +reaches Roomote, use Caddy's internal certificates instead of requesting +Let's Encrypt certificates: + +```sh +curl -fsSL https://get.roomote.dev | bash -s -- \ + --domain roomote.internal \ + --tls-mode internal +``` + +`--tls-mode internal` skips public-DNS verification and configures Caddy to +issue local certificates; it requires `--domain`. Direct private-network +clients must trust Caddy's local CA, while clients behind a tunnel use the +tunnel's public certificate. Use `--skip-dns-check` with the default `acme` +mode only when you know public DNS is not ready yet. The selected mode is +retained in `/opt/roomote/.env`, so installer reruns and `roomote upgrade` +preserve it. + Useful flags and env vars: `--version ` pins a release, and `--preview-domain ` overrides the preview root. Both published images are public, so pulls need no credentials. Re-running the installer is safe: @@ -294,6 +314,9 @@ common provider keys into worker containers: - `MOONSHOT_API_KEY` - `KIMI_API_KEY` (Kimi for Coding, `kimi-for-coding/...` models) - `MINIMAX_API_KEY` +- `ZAI_API_KEY` (with `ZAI_REGION`: `global` or `china`, defaults to `global`) +- `ZAI_CODING_PLAN_API_KEY` (Z.AI Coding Plan, `zai-coding-plan/...` models, + with `ZAI_CODING_PLAN_REGION`) - `OPENCODE_API_KEY` If your provider uses another env var name, list it in @@ -582,9 +605,9 @@ using Docker sandboxes. That overlay: and, under the default `internet` egress policy, blackholes private and cloud metadata ranges plus drops packets destined to the Docker bridge gateway (host hairpin) while still allowing public egress via that gateway - as next-hop. The gateway drop is installed with `iptables`, so the worker - image must ship an iptables binary the host kernel supports (the stock - image does); sandbox provisioning fails closed when it is unavailable; + as next-hop. The gateway drop automatically selects an nftables or legacy + `iptables` backend supported by the host kernel; sandbox provisioning fails + closed when neither backend is available; - points `DOCKER_WORKER_RELEASE_PATH` at the controller image's packaged worker release archive. diff --git a/apps/api/package.json b/apps/api/package.json index 4f5c2289a..ff1b27170 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -18,7 +18,7 @@ "dependencies": { "@aws-sdk/client-s3": "3.1000.0", "@aws-sdk/s3-request-presigner": "3.1000.0", - "@hono/node-server": "1.19.13", + "@hono/node-server": "2.0.5", "@hono/trpc-server": "^0.4.2", "@modelcontextprotocol/sdk": "^1.29.0", "@octokit/openapi-webhooks-types": "^12.1.0", diff --git a/apps/api/src/__tests__/sentry.test.ts b/apps/api/src/__tests__/sentry.test.ts index 69e34ab61..e3439724d 100644 --- a/apps/api/src/__tests__/sentry.test.ts +++ b/apps/api/src/__tests__/sentry.test.ts @@ -39,15 +39,23 @@ import { HTTPException } from 'hono/http-exception'; import { createApiApp } from '../server'; +const originalRAppEnv = process.env.R_APP_ENV; + describe('API Sentry integration', () => { beforeEach(() => { vi.clearAllMocks(); process.env.APP_ENV = 'preview'; + process.env.R_APP_ENV = 'preview'; process.env.API_SENTRY_DSN = 'https://api.example/1'; }); afterEach(() => { delete process.env.APP_ENV; + if (originalRAppEnv === undefined) { + delete process.env.R_APP_ENV; + } else { + process.env.R_APP_ENV = originalRAppEnv; + } delete process.env.API_SENTRY_DSN; }); diff --git a/apps/api/src/handlers/discord/__tests__/event-gate.test.ts b/apps/api/src/handlers/discord/__tests__/event-gate.test.ts index 89b63ed52..327894cc8 100644 --- a/apps/api/src/handlers/discord/__tests__/event-gate.test.ts +++ b/apps/api/src/handlers/discord/__tests__/event-gate.test.ts @@ -10,6 +10,7 @@ import { claimDiscordApiEvent, completeDiscordApiEvent, releaseDiscordApiEvent, + renewDiscordApiEvent, } from '../event-gate.js'; describe('Discord API event gate', () => { @@ -79,4 +80,24 @@ describe('Discord API event gate', () => { 'processing:owner', ); }); + + it('renews only the lease owner token', async () => { + mocks.eval.mockResolvedValue(1); + + await expect( + renewDiscordApiEvent({ + eventType: 'MESSAGE_CREATE', + eventId: 'message-1', + token: 'processing:owner', + }), + ).resolves.toBe(true); + + expect(mocks.eval).toHaveBeenCalledWith( + expect.stringContaining("redis.call('EXPIRE'"), + 1, + 'discord:api:event:MESSAGE_CREATE:message-1', + 'processing:owner', + '300', + ); + }); }); diff --git a/apps/api/src/handlers/discord/__tests__/index.test.ts b/apps/api/src/handlers/discord/__tests__/index.test.ts index 6e1f2992b..d6bba85f3 100644 --- a/apps/api/src/handlers/discord/__tests__/index.test.ts +++ b/apps/api/src/handlers/discord/__tests__/index.test.ts @@ -10,6 +10,7 @@ const mocks = vi.hoisted(() => ({ claimEvent: vi.fn(), completeEvent: vi.fn(), releaseEvent: vi.fn(), + renewEvent: vi.fn(), resolveProvider: vi.fn(), findMappedUserId: vi.fn(), findInstallation: vi.fn(), @@ -67,7 +68,9 @@ vi.mock('@roomote/redis', async (importOriginal) => { vi.mock('../event-gate.js', () => ({ claimDiscordApiEvent: mocks.claimEvent, completeDiscordApiEvent: mocks.completeEvent, + discordApiEventLeaseRenewal: { intervalMs: 60 * 1000 }, releaseDiscordApiEvent: mocks.releaseEvent, + renewDiscordApiEvent: mocks.renewEvent, })); vi.mock('../provider.js', () => { @@ -148,6 +151,7 @@ vi.mock('@roomote/cloud-agents/server', () => ({ })); import { discord, discordGatewayEventProcessingTimeout } from '../index.js'; +import { discordApiEventLeaseRenewal } from '../event-gate.js'; const app = new Hono(); app.route('/api/internal/discord', discord); @@ -219,6 +223,7 @@ describe('Discord Gateway event handler', () => { }); mocks.completeEvent.mockResolvedValue(true); mocks.releaseEvent.mockResolvedValue(undefined); + mocks.renewEvent.mockResolvedValue(true); mocks.resolveProvider.mockResolvedValue({ applicationId: 'app-1', botToken: 'never-exposed-token', @@ -477,11 +482,23 @@ describe('Discord Gateway event handler', () => { }, ); - it('times out slow processing and releases the event lease for a retry', async () => { + it('keeps a timed-out event lease until slow processing finishes', async () => { const originalTimeout = discordGatewayEventProcessingTimeout.timeoutMs; + const originalLeaseRenewalInterval = discordApiEventLeaseRenewal.intervalMs; vi.useFakeTimers(); discordGatewayEventProcessingTimeout.timeoutMs = 10; - mocks.getChannel.mockImplementation(() => new Promise(() => undefined)); + discordApiEventLeaseRenewal.intervalMs = 15; + let resolveChannel: (value: { + id: string; + name: string; + type: number; + }) => void; + mocks.getChannel.mockImplementation( + () => + new Promise((resolve) => { + resolveChannel = resolve; + }), + ); try { const responsePromise = postEvent(envelope(message())); @@ -493,13 +510,27 @@ describe('Discord Gateway event handler', () => { ok: false, error: 'discord_api_unavailable', }); - expect(mocks.releaseEvent).toHaveBeenCalledWith({ + expect(mocks.releaseEvent).not.toHaveBeenCalled(); + expect(mocks.completeEvent).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(15); + expect(mocks.renewEvent).toHaveBeenCalledWith({ + eventType: 'MESSAGE_CREATE', + eventId: 'message-1', + token: 'claim-token', + }); + + resolveChannel!({ id: 'dm-1', name: 'Direct message', type: 1 }); + await vi.runAllTimersAsync(); + + expect(mocks.completeEvent).toHaveBeenCalledWith({ eventType: 'MESSAGE_CREATE', eventId: 'message-1', token: 'claim-token', }); } finally { discordGatewayEventProcessingTimeout.timeoutMs = originalTimeout; + discordApiEventLeaseRenewal.intervalMs = originalLeaseRenewalInterval; vi.useRealTimers(); } }); diff --git a/apps/api/src/handlers/discord/event-gate.ts b/apps/api/src/handlers/discord/event-gate.ts index cc01557eb..0ec68c367 100644 --- a/apps/api/src/handlers/discord/event-gate.ts +++ b/apps/api/src/handlers/discord/event-gate.ts @@ -6,6 +6,10 @@ const DISCORD_API_EVENT_DEDUPE_PREFIX = 'discord:api:event:'; const DISCORD_API_EVENT_DEDUPE_TTL_SECONDS = 7 * 24 * 60 * 60; const DISCORD_API_EVENT_PROCESSING_TTL_SECONDS = 5 * 60; +export const discordApiEventLeaseRenewal = { + intervalMs: 60 * 1000, +}; + const CLAIM_EVENT_SCRIPT = ` local existing = redis.call('GET', KEYS[1]) if existing == 'done' then @@ -40,6 +44,13 @@ end return redis.call('DEL', KEYS[1]) `; +const RENEW_EVENT_SCRIPT = ` +if redis.call('GET', KEYS[1]) ~= ARGV[1] then + return false +end +return redis.call('EXPIRE', KEYS[1], ARGV[2]) +`; + function eventKey(eventType: string, eventId: string): string { return `${DISCORD_API_EVENT_DEDUPE_PREFIX}${eventType}:${eventId}`; } @@ -96,3 +107,19 @@ export async function releaseDiscordApiEvent(input: { ); return result === 1; } + +/** Keep an owned lease from expiring while its handler is still running. */ +export async function renewDiscordApiEvent(input: { + eventType: string; + eventId: string; + token: string; +}): Promise { + const result = await getRedis().eval( + RENEW_EVENT_SCRIPT, + 1, + eventKey(input.eventType, input.eventId), + input.token, + DISCORD_API_EVENT_PROCESSING_TTL_SECONDS.toString(), + ); + return result === 1; +} diff --git a/apps/api/src/handlers/discord/index.ts b/apps/api/src/handlers/discord/index.ts index 1136699bd..a01f26cfd 100644 --- a/apps/api/src/handlers/discord/index.ts +++ b/apps/api/src/handlers/discord/index.ts @@ -61,7 +61,9 @@ import { maybeHandleDiscordChannelAutoStart } from './channel-auto-start.js'; import { claimDiscordApiEvent, completeDiscordApiEvent, + discordApiEventLeaseRenewal, releaseDiscordApiEvent, + renewDiscordApiEvent, } from './event-gate.js'; import { DiscordProviderNotConfiguredError, @@ -118,6 +120,22 @@ function withDiscordGatewayEventProcessingTimeout( }); } +function renewDiscordApiEventLease(input: { + eventType: string; + eventId: string; + token: string; +}): () => void { + const interval = setInterval(() => { + void renewDiscordApiEvent(input).catch((error) => { + apiLogger.warn( + `[discord] Failed to renew event lease ${input.eventId}: ${error instanceof Error ? error.message : String(error)}`, + ); + }); + }, discordApiEventLeaseRenewal.intervalMs); + interval.unref(); + return () => clearInterval(interval); +} + const DISCORD_HELP_MESSAGE = [ "👋 I'm Roomote. Mention me in a server channel or message me directly to start a task.", '', @@ -937,10 +955,12 @@ discord.post('/events/process', async (c) => { if (claim.status === 'processing') { return c.json({ ok: false, error: 'discord_event_in_progress' }, 425); } + const eventLease = { ...eventRef, token: claim.token }; + const stopLeaseRenewal = renewDiscordApiEventLease(eventLease); + const processing = processDiscordGatewayEvent(parsed.event); try { - const result = await withDiscordGatewayEventProcessingTimeout( - processDiscordGatewayEvent(parsed.event), - ); + const result = await withDiscordGatewayEventProcessingTimeout(processing); + stopLeaseRenewal(); // The queue retries processing failures. If this bookkeeping write is // unavailable, the short lease expires and a later job attempt can claim it. await completeDiscordApiEvent({ ...eventRef, token: claim.token }).catch( @@ -952,6 +972,32 @@ discord.post('/events/process', async (c) => { ); return c.json(result); } catch (error) { + if (error instanceof DiscordGatewayEventProcessingTimeoutError) { + // The timeout only ends this HTTP request. Keep the lease until the + // original handler settles so a retry cannot start duplicate work. + void processing + .then( + () => + completeDiscordApiEvent(eventLease).catch((completionError) => { + apiLogger.warn( + `[discord] Failed to mark timed-out event ${eventRef.eventId} complete: ${completionError instanceof Error ? completionError.message : String(completionError)}`, + ); + }), + (processingError: unknown) => { + const finalize = isPermanentDiscordEventError(processingError) + ? completeDiscordApiEvent(eventLease) + : releaseDiscordApiEvent(eventLease); + return finalize.catch(() => undefined); + }, + ) + .finally(stopLeaseRenewal); + apiLogger.warn( + `[discord] Discord API unavailable while processing event ${eventRef.eventId}: ${error.message}`, + ); + return c.json({ ok: false, error: 'discord_api_unavailable' }, 503); + } + + stopLeaseRenewal(); if (isPermanentDiscordEventError(error)) { await completeDiscordApiEvent({ ...eventRef, @@ -976,10 +1022,7 @@ discord.post('/events/process', async (c) => { 503, ); } - if ( - error instanceof DiscordGatewayEventProcessingTimeoutError || - isRetryableDiscordProviderError(error) - ) { + if (isRetryableDiscordProviderError(error)) { apiLogger.warn( `[discord] Discord API unavailable while processing event ${eventRef.eventId}: ${error.message}`, ); diff --git a/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts b/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts index e92912bbb..0f9dacd32 100644 --- a/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts +++ b/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts @@ -119,9 +119,15 @@ describe('inference gateway', () => { const nameList = typeof names === 'string' ? [names] : names; // Region lookups resolve separately from API keys. - return nameList.includes('AWS_REGION') - ? undefined - : 'provider-secret-key'; + if ( + nameList.includes('AWS_REGION') || + nameList.includes('ZAI_REGION') || + nameList.includes('ZAI_CODING_PLAN_REGION') + ) { + return undefined; + } + + return 'provider-secret-key'; }, ); }); @@ -281,6 +287,103 @@ describe('inference gateway', () => { ); }); + it('proxies Z.AI to the international v4 chat completions endpoint by default', async () => { + const fetchMock = stubUpstreamFetch(); + const response = await postMessages( + createApp(createRunToken()), + '/api/inference/zai/chat/completions', + ); + + expect(response.status).toBe(200); + + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(url).toBe('https://api.z.ai/api/paas/v4/chat/completions'); + expect(new Headers(init.headers).get('authorization')).toBe( + 'Bearer provider-secret-key', + ); + }); + + it('proxies Z.AI to the China host when ZAI_REGION is china', async () => { + mockResolveModelProviderEnvValue.mockImplementation( + async (names: string | readonly string[]) => { + const nameList = typeof names === 'string' ? [names] : names; + if (nameList.includes('ZAI_REGION')) { + return 'china'; + } + if ( + nameList.includes('AWS_REGION') || + nameList.includes('ZAI_CODING_PLAN_REGION') + ) { + return undefined; + } + return 'provider-secret-key'; + }, + ); + + const fetchMock = stubUpstreamFetch(); + const response = await postMessages( + createApp(createRunToken()), + '/api/inference/zai/chat/completions', + ); + + expect(response.status).toBe(200); + const [url] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(url).toBe('https://open.bigmodel.cn/api/paas/v4/chat/completions'); + }); + + it('proxies Z.AI Coding Plan to its international coding endpoint', async () => { + const fetchMock = stubUpstreamFetch(); + const response = await postMessages( + createApp(createRunToken()), + '/api/inference/zai-coding-plan/chat/completions', + ); + + expect(response.status).toBe(200); + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(url).toBe('https://api.z.ai/api/coding/paas/v4/chat/completions'); + expect(new Headers(init.headers).get('authorization')).toBe( + 'Bearer provider-secret-key', + ); + }); + + it('rejects OpenAI-style /v1 paths on Z.AI', async () => { + const fetchMock = stubUpstreamFetch(); + const response = await postMessages( + createApp(createRunToken()), + '/api/inference/zai/v1/chat/completions', + ); + + expect(response.status).toBe(403); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it('rejects a Z.AI region with no configured upstream base', async () => { + mockResolveModelProviderEnvValue.mockImplementation( + async (names: string | readonly string[]) => { + const nameList = typeof names === 'string' ? [names] : names; + if (nameList.includes('ZAI_REGION')) { + return 'us-east-1'; + } + if ( + nameList.includes('AWS_REGION') || + nameList.includes('ZAI_CODING_PLAN_REGION') + ) { + return undefined; + } + return 'provider-secret-key'; + }, + ); + + const fetchMock = stubUpstreamFetch(); + const response = await postMessages( + createApp(createRunToken()), + '/api/inference/zai/chat/completions', + ); + + expect(response.status).toBe(500); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it('proxies GitHub Copilot without a /v1 base-path suffix', async () => { mockGetGitHubCopilotAccessToken.mockResolvedValue('github-oauth-token'); const fetchMock = stubUpstreamFetch(); diff --git a/apps/api/src/handlers/inference/registry.ts b/apps/api/src/handlers/inference/registry.ts index 47e29c074..fdf8a325f 100644 --- a/apps/api/src/handlers/inference/registry.ts +++ b/apps/api/src/handlers/inference/registry.ts @@ -212,6 +212,20 @@ async function resolveProviderUpstreamBaseUrl( (await resolveModelProviderEnvValue([provider.region.envVarName])) ?? provider.region.default; + // Providers with discrete regional hosts select a base outright; the + // `{region}` template and its cloud-region pattern do not apply to them. + if (provider.region.baseUrls) { + const baseUrl = provider.region.baseUrls[region]; + + if (!baseUrl) { + throw new Error( + `${provider.region.envVarName} must be one of ${Object.keys(provider.region.baseUrls).join(', ')} for ${provider.name}. Received "${region}".`, + ); + } + + return baseUrl; + } + if (!INFERENCE_GATEWAY_REGION_PATTERN.test(region)) { throw new Error( `${provider.region.envVarName} must be a valid region for ${provider.name}. Received "${region}".`, diff --git a/apps/api/src/monitoring/sentry.test.ts b/apps/api/src/monitoring/sentry.test.ts index df681613d..cba3ecc4c 100644 --- a/apps/api/src/monitoring/sentry.test.ts +++ b/apps/api/src/monitoring/sentry.test.ts @@ -20,7 +20,7 @@ describe('api sentry monitoring', () => { }); it('disables Sentry when the app environment is development', async () => { - process.env.APP_ENV = 'development'; + process.env.R_APP_ENV = 'development'; const { initApiSentry } = await import('./sentry'); @@ -46,7 +46,7 @@ describe('api sentry monitoring', () => { }); it('enables Sentry outside development only when a DSN is configured', async () => { - process.env.APP_ENV = 'production'; + process.env.R_APP_ENV = 'production'; process.env.API_SENTRY_DSN = 'https://api.example/1'; const { initApiSentry } = await import('./sentry'); @@ -62,7 +62,7 @@ describe('api sentry monitoring', () => { }); it('stays disabled outside development when no DSN is configured', async () => { - process.env.APP_ENV = 'production'; + process.env.R_APP_ENV = 'production'; const { initApiSentry } = await import('./sentry'); diff --git a/apps/bullmq/package.json b/apps/bullmq/package.json index ffc7f7d7e..b6b57433b 100644 --- a/apps/bullmq/package.json +++ b/apps/bullmq/package.json @@ -21,7 +21,7 @@ "@bull-board/api": "^6.16.4", "@bull-board/hono": "^6.16.4", "@bull-board/ui": "^6.16.4", - "@hono/node-server": "1.19.13", + "@hono/node-server": "2.0.5", "@roomote/cloud-agents": "workspace:^", "@roomote/communication": "workspace:^", "@roomote/compute-providers": "workspace:^", diff --git a/apps/controller/src/compute-providers/__tests__/docker-sandbox-security.test.ts b/apps/controller/src/compute-providers/__tests__/docker-sandbox-security.test.ts index b15c113c5..7a74d5c0b 100644 --- a/apps/controller/src/compute-providers/__tests__/docker-sandbox-security.test.ts +++ b/apps/controller/src/compute-providers/__tests__/docker-sandbox-security.test.ts @@ -280,10 +280,17 @@ describe('attachDockerEgressPolicy', () => { 'ip route replace blackhole "$gateway/32"', ); expect(routeScript).toContain( - 'iptables -C OUTPUT -d "$gateway" -j DROP 2>/dev/null || iptables -A OUTPUT -d "$gateway" -j DROP', + 'for candidate in iptables-nft iptables-legacy iptables; do', ); + expect(routeScript).toContain('"$candidate" -S OUTPUT >/dev/null 2>&1'); expect(routeScript).toContain( - 'iptables -C FORWARD -d "$gateway" -j DROP 2>/dev/null || iptables -A FORWARD -d "$gateway" -j DROP', + '"$iptables_cmd" -C OUTPUT -d "$gateway" -j DROP 2>/dev/null || "$iptables_cmd" -A OUTPUT -d "$gateway" -j DROP', + ); + expect(routeScript).toContain( + '"$iptables_cmd" -C FORWARD -d "$gateway" -j DROP 2>/dev/null || "$iptables_cmd" -A FORWARD -d "$gateway" -j DROP', + ); + expect(routeScript).toContain( + 'no supported iptables backend (nft, legacy, or default)', ); // Upgrades must heal netns state left by controllers that blackholed the // gateway route in retained standby workers. diff --git a/apps/controller/src/compute-providers/docker-sandbox-security.ts b/apps/controller/src/compute-providers/docker-sandbox-security.ts index 1e6c27319..f31d8cd36 100644 --- a/apps/controller/src/compute-providers/docker-sandbox-security.ts +++ b/apps/controller/src/compute-providers/docker-sandbox-security.ts @@ -541,9 +541,8 @@ export async function attachDockerEgressPolicy( 'ALL', '--cap-add', 'NET_ADMIN', - // iptables-legacy (forced in the worker image) opens a raw socket, which - // needs CAP_NET_RAW on top of CAP_NET_ADMIN; both are in Docker's default - // capability set. + // Legacy iptables needs CAP_NET_RAW on top of CAP_NET_ADMIN. Keep both + // capabilities because the helper selects the supported backend at runtime. '--cap-add', 'NET_RAW', '--entrypoint', @@ -573,18 +572,27 @@ export async function attachDockerEgressPolicy( // the gateway as a route; retained standby workers keep their // netns across controller upgrades. ' ip route del blackhole "$gateway/32" 2>/dev/null || true', - ' if ! command -v iptables >/dev/null 2>&1; then', - ' if command -v apk >/dev/null 2>&1; then', - ' apk add --no-cache iptables >/dev/null', - ' fi', + ' find_iptables() {', + ' for candidate in iptables-nft iptables-legacy iptables; do', + ' if command -v "$candidate" >/dev/null 2>&1 && "$candidate" -S OUTPUT >/dev/null 2>&1; then', + ' printf "%s" "$candidate"', + ' return 0', + ' fi', + ' done', + ' return 1', + ' }', + ' iptables_cmd="$(find_iptables || true)"', + ' if [ -z "$iptables_cmd" ] && command -v apk >/dev/null 2>&1; then', + ' apk add --no-cache iptables >/dev/null', + ' iptables_cmd="$(find_iptables || true)"', ' fi', - ' if command -v iptables >/dev/null 2>&1; then', - ' iptables -C OUTPUT -d "$gateway" -j DROP 2>/dev/null || iptables -A OUTPUT -d "$gateway" -j DROP', + ' if [ -n "$iptables_cmd" ]; then', + ' "$iptables_cmd" -C OUTPUT -d "$gateway" -j DROP 2>/dev/null || "$iptables_cmd" -A OUTPUT -d "$gateway" -j DROP', // The route blackhole also covered forwarded traffic; keep that // property in case the worker netns ever routes packets. - ' iptables -C FORWARD -d "$gateway" -j DROP 2>/dev/null || iptables -A FORWARD -d "$gateway" -j DROP', + ' "$iptables_cmd" -C FORWARD -d "$gateway" -j DROP 2>/dev/null || "$iptables_cmd" -A FORWARD -d "$gateway" -j DROP', ' else', - ' echo "iptables unavailable; cannot block docker gateway $gateway" >&2', + ' echo "no supported iptables backend (nft, legacy, or default); cannot block docker gateway $gateway" >&2', ' exit 1', ' fi', 'fi', diff --git a/apps/controller/src/compute-providers/spawn-docker-worker.ts b/apps/controller/src/compute-providers/spawn-docker-worker.ts index d1e8808f9..096ee940c 100644 --- a/apps/controller/src/compute-providers/spawn-docker-worker.ts +++ b/apps/controller/src/compute-providers/spawn-docker-worker.ts @@ -975,8 +975,9 @@ export function getDockerWorkerCommand( /** * Docker Compose self-host/prod attaches the `api` service to each task - * network. When a control network is configured, egress policy blackholes the - * docker bridge gateway so sandboxes cannot hairpin through the public edge. + * network. When a control network is configured, egress policy blocks packets + * destined for the Docker bridge gateway so sandboxes cannot hairpin through + * the public edge. * Workers must call the in-network API alias directly (no `/_roomote-api` * prefix — that path only exists on the public reverse proxy). */ diff --git a/apps/docs/environment-variables.mdx b/apps/docs/environment-variables.mdx index b41ec04d4..358b04856 100644 --- a/apps/docs/environment-variables.mdx +++ b/apps/docs/environment-variables.mdx @@ -184,6 +184,10 @@ as per-task auth tokens or workspace paths. | `MOONSHOT_API_KEY` | Provider key | Moonshot AI / Kimi Open Platform API key. | | `KIMI_API_KEY` | Provider key | Kimi for Coding API key for `kimi-for-coding/...` models. | | `MINIMAX_API_KEY` | Provider key | MiniMax API key. | +| `ZAI_API_KEY` | Provider key | Z.AI platform API key for the region set by `ZAI_REGION`. | +| `ZAI_REGION` | Provider key | Z.AI region: `global` (International) or `china`. Defaults to `global` when unset. | +| `ZAI_CODING_PLAN_API_KEY` | Provider key | Z.AI Coding Plan API key for `zai-coding-plan/...` models. Not a general platform key. | +| `ZAI_CODING_PLAN_REGION` | Provider key | Z.AI Coding Plan region: `global` (International) or `china`. Defaults to `global` when unset. | | `OPENCODE_API_KEY` | Provider key | OpenCode Zen / Go API key. | | `GEMINI_API_KEY` | Provider key | Google Gemini API key. Can also be saved from **Settings > Models**. | | `GOOGLE_GENERATIVE_AI_API_KEY` | Provider key | Alternate Google/Gemini provider key forwarded when configured or inferred. | diff --git a/apps/docs/models.mdx b/apps/docs/models.mdx index bc7de68fa..31373bace 100644 --- a/apps/docs/models.mdx +++ b/apps/docs/models.mdx @@ -16,9 +16,10 @@ Configure models from **Settings > Models**. An inference provider is the service that hosts or routes model calls. Roomote supports providers such as OpenRouter, Vercel AI Gateway, Baseten, -Together AI, OpenAI, Anthropic, Moonshot AI, Kimi for Coding, MiniMax, OpenCode, -Amazon Bedrock, Google Gemini, xAI, GitHub Copilot, ChatGPT subscriptions, and -OpenAI-compatible endpoints such as LiteLLM, Ollama, and vLLM. +Together AI, OpenAI, Anthropic, Moonshot AI, Kimi for Coding, MiniMax, Z.AI, +Z.AI Coding Plan, OpenCode, Amazon Bedrock, Google Gemini, xAI, GitHub Copilot, +ChatGPT subscriptions, and OpenAI-compatible endpoints such as LiteLLM, Ollama, +and vLLM. You can connect more than one inference provider in the same deployment. That lets you mix and match models by provider instead of betting the whole @@ -117,8 +118,8 @@ R_EXPLORE_MODEL=openrouter/openai/gpt-5.6-luna Roomote automatically forwards common provider keys to task workers, including OpenRouter, Vercel AI Gateway, OpenAI, Anthropic, Google Gemini, -Moonshot, Kimi for Coding, MiniMax, OpenCode, Amazon Bedrock, xAI, and GitHub -Copilot keys. +Moonshot, Kimi for Coding, MiniMax, Z.AI, OpenCode, Amazon Bedrock, xAI, and +GitHub Copilot keys. Use `R_MODEL_ENV_KEYS` when a provider key uses a custom env var name: ```sh diff --git a/apps/web/package.json b/apps/web/package.json index 769d11f91..0a7032103 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,7 +23,7 @@ "dependencies": { "@aws-sdk/client-s3": "3.1000.0", "@aws-sdk/s3-request-presigner": "3.1000.0", - "@better-auth/drizzle-adapter": "^1.6.19", + "@better-auth/drizzle-adapter": "^1.6.22", "@hookform/resolvers": "^5.2.2", "@linear/sdk": "^68.0.0", "@melloware/react-logviewer": "^6.4.1", @@ -80,7 +80,7 @@ "@zxcvbn-ts/core": "^4.1.2", "ai": "^6.0.116", "ansi-to-react": "^6.2.6", - "better-auth": "^1.6.19", + "better-auth": "^1.6.22", "better-sse": "^0.16.1", "change-case": "^5.4.4", "class-variance-authority": "^0.7.1", diff --git a/apps/web/src/app/(onboarding)/setup/StepInferenceProvider.tsx b/apps/web/src/app/(onboarding)/setup/StepInferenceProvider.tsx index 94708e1e0..5b653c696 100644 --- a/apps/web/src/app/(onboarding)/setup/StepInferenceProvider.tsx +++ b/apps/web/src/app/(onboarding)/setup/StepInferenceProvider.tsx @@ -6,6 +6,8 @@ import { toast } from 'sonner'; import { CHATGPT_SUBSCRIPTION_PROVIDER_ID, OPENAI_COMPATIBLE_PROVIDER_ID, + getDefaultAdditionalEnvValues, + getSetupModelProvider, type SetupModelProviderId, type SetupModelStatus, } from '@roomote/types'; @@ -23,6 +25,7 @@ import { SelectValue, Spinner, } from '@/components/system'; +import { AdditionalEnvFieldInput } from '@/components/settings/AdditionalEnvFieldInput'; import { ChatGptConnectDialog } from '@/components/settings/ChatGptConnectDialog'; import { GitHubCopilotConnectDialog } from '@/components/settings/GitHubCopilotConnectDialog'; @@ -133,7 +136,16 @@ export function StepInferenceProvider({ : '', ); setConnectionName(''); - setAdditionalEnvValues({}); + // Seeded from the catalog rather than the fetched status so this effect + // stays keyed on `selectedProvider` alone; depending on the status query + // would reset in-progress input on every refetch. + setAdditionalEnvValues( + getDefaultAdditionalEnvValues( + selectedProvider + ? (getSetupModelProvider(selectedProvider).additionalEnvFields ?? []) + : [], + ), + ); setEditingSavedValue(false); setIsChatGptDialogOpen(false); setIsGitHubCopilotDialogOpen(false); @@ -452,19 +464,18 @@ export function StepInferenceProvider({ {field.label} {field.required ? '' : ' (optional)'} - + onValueChange={(value) => setAdditionalEnvValues((values) => ({ ...values, - [field.envVarName]: event.target.value, + [field.envVarName]: value, })) } - placeholder={field.placeholder} disabled={saveModelConfig.isPending} - aria-label={`${field.label} for ${selectedProviderStatus?.label ?? 'provider'}`} - data-1p-ignore + ariaLabel={`${field.label} for ${selectedProviderStatus?.label ?? 'provider'}`} + selectTriggerClassName="min-w-44" /> ))} diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/__tests__/acp-protocol-service.client.test.ts b/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/__tests__/acp-protocol-service.client.test.ts index 900ef721e..55a580f84 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/__tests__/acp-protocol-service.client.test.ts +++ b/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/__tests__/acp-protocol-service.client.test.ts @@ -47,6 +47,30 @@ function toolCallUpdate(sequence: number): AcpMessage { }; } +function subagentActivityUpdate( + sequence: number, + lastMessage: string, +): AcpMessage { + return { + id: `opencode-server:${sequence}`, + ts: 1000 + sequence, + eventType: ACP_ENVELOPE_EVENT_TYPES.ToolCallUpdate, + role: 'tool', + kind: 'tool_result', + contentBlocks: [], + metadata: { + sessionId: 'session-opencode', + turnId: 'message-opencode', + }, + payload: { + toolCallId: 'subagent-call-1', + kind: 'subagent', + status: 'in_progress', + subagentActivity: { lastMessage }, + }, + }; +} + describe('AcpProtocolService', () => { it('continues a partial assistant stream after active stream state is rebuilt', () => { const service = new AcpProtocolService(); @@ -114,4 +138,106 @@ describe('AcpProtocolService', () => { partial: true, }); }); + + it('keeps an OpenCode subagent prompt from rawInput when its result arrives', () => { + const service = new AcpProtocolService(); + const metadata = { + sessionId: 'session-opencode', + turnId: 'message-opencode', + }; + const toolCall: AcpMessage = { + id: 'opencode-server:1', + ts: 1001, + eventType: ACP_ENVELOPE_EVENT_TYPES.ToolCall, + role: 'tool', + kind: 'tool_call', + contentBlocks: [], + metadata, + payload: { + toolCallId: 'subagent-call-1', + kind: 'subagent', + title: 'Launch explorer', + status: 'in_progress', + rawInput: { + prompt: 'Inspect the task transcript implementation.', + subagent_type: 'explore', + }, + }, + }; + const toolResult: AcpMessage = { + id: 'opencode-server:2', + ts: 1002, + eventType: ACP_ENVELOPE_EVENT_TYPES.ToolResult, + role: 'tool', + kind: 'tool_result', + contentBlocks: [], + metadata, + payload: { + toolCallId: 'subagent-call-1', + kind: 'subagent', + title: 'Launch explorer', + status: 'completed', + output: 'The transcript renderer owns nested subagent rows.', + }, + }; + + const initial = service.applyOutputEvent([], toolCall)!.acpMessages; + const completed = service.applyOutputEvent( + initial, + toolResult, + )!.acpMessages; + + expect(completed).toHaveLength(1); + expect(completed[0]).toMatchObject({ + kind: 'tool_result', + data: { + prompt: 'Inspect the task transcript implementation.', + output: 'The transcript renderer owns nested subagent rows.', + }, + }); + }); + + it('merges live child activity into an in-progress subagent row', () => { + const service = new AcpProtocolService(); + const toolCall: AcpMessage = { + id: 'opencode-server:1', + ts: 1001, + eventType: ACP_ENVELOPE_EVENT_TYPES.ToolCall, + role: 'tool', + kind: 'tool_call', + contentBlocks: [], + metadata: { + sessionId: 'session-opencode', + turnId: 'message-opencode', + }, + payload: { + toolCallId: 'subagent-call-1', + kind: 'subagent', + title: 'Launch explorer', + status: 'in_progress', + prompt: 'Inspect the task transcript implementation.', + }, + }; + + const initial = service.applyOutputEvent([], toolCall)!.acpMessages; + const updated = service.applyOutputEvent( + initial, + subagentActivityUpdate( + 2, + 'The child agent is reviewing the render path.', + ), + )!.acpMessages; + + expect(updated).toHaveLength(1); + expect(updated[0]).toMatchObject({ + kind: 'tool_result', + partial: true, + data: { + prompt: 'Inspect the task transcript implementation.', + subagentActivity: { + lastMessage: 'The child agent is reviewing the render path.', + }, + }, + }); + }); }); diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/acp-protocol-service.ts b/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/acp-protocol-service.ts index f8db729c0..6731bbb8d 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/acp-protocol-service.ts +++ b/apps/web/src/app/(sandbox)/task/[taskId]/hooks/services/acp-protocol-service.ts @@ -615,6 +615,9 @@ function mergeToolResultPayload( incoming: AcpToolResultUiMessage, ): AcpToolResultPayload { const existingData = existing.data; + const existingRawInput = asRecord( + (existingData as unknown as Record).rawInput, + ); const mergedPayload: AcpToolResultPayload = { ...incoming.data, kind: incoming.data.kind ?? existingData.kind, @@ -630,7 +633,10 @@ function mergeToolResultPayload( receiverThreadIds: incoming.data.receiverThreadIds ?? existingData.receiverThreadIds, agentsStates: incoming.data.agentsStates ?? existingData.agentsStates, - prompt: incoming.data.prompt ?? existingData.prompt, + prompt: + incoming.data.prompt ?? + existingData.prompt ?? + asString(existingRawInput?.prompt), agentType: incoming.data.agentType ?? existingData.agentType, model: incoming.data.model ?? existingData.model, reasoningEffort: diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/AcpToolDetails.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/AcpToolDetails.tsx index 3f4a21ba0..62416be0a 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/AcpToolDetails.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/AcpToolDetails.tsx @@ -3,11 +3,22 @@ import { sanitizeSandboxPathString, } from '@/lib'; -import { CodeBlock, ToolInput } from '@/components/ai-elements'; +import { + CodeBlock, + MessageResponse, + ToolInput, +} from '@/components/ai-elements'; +import { + ChevronRight, + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/system'; import type { AcpToolCallUiMessage, AcpToolResultUiMessage } from './types'; import { isSubagentToolPayload } from './subagent-tool'; import { + getSubagentLastMessage, getSubagentPrompt, hidesExpandedToolResult, } from './tool-detail-visibility'; @@ -33,6 +44,7 @@ export function AcpToolDetails({ : msg.text; const isSubagent = isSubagentToolPayload(msg.data); const subagentPrompt = getSubagentPrompt(msg); + const subagentLastMessage = getSubagentLastMessage(msg); if (isSubagent && showSubagentPayload) { return ( @@ -46,13 +58,31 @@ export function AcpToolDetails({ ); } - if (isSubagent && subagentPrompt) { + if (isSubagent && (subagentPrompt || subagentLastMessage)) { return (
- {sanitizeSandboxPathString(subagentPrompt)} + {subagentLastMessage ? ( + + {sanitizeSandboxPathString(subagentLastMessage)} + + ) : null} + {subagentPrompt ? ( + + + + Prompt + + + {sanitizeSandboxPathString(subagentPrompt)} + + + ) : null}
); } diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/__tests__/AcpToolDetails.client.test.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/__tests__/AcpToolDetails.client.test.tsx index 85dad6aec..56f18cf1f 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/__tests__/AcpToolDetails.client.test.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/__tests__/AcpToolDetails.client.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import type { ReactNode } from 'react'; import { AcpToolDetails } from '../AcpToolDetails'; @@ -21,6 +21,9 @@ vi.mock('@/components/ai-elements', () => ({ toolInputSpy(props); return
tool input
; }, + MessageResponse: (props: { children?: ReactNode }) => ( +
{props.children}
+ ), })); function buildMessage( @@ -58,11 +61,12 @@ describe('AcpToolDetails', () => { toolInputSpy.mockClear(); }); - it('shows the subagent launch prompt when expanding without debug mode', () => { + it('leads with the most recent message and collapses the launch prompt', () => { render( { />, ); + expect( + screen.getByText('The branch is clean and the relevant tests pass.'), + ).toBeInTheDocument(); + expect( + screen.queryByText('Review the current branch and summarize the state.'), + ).not.toBeInTheDocument(); + + fireEvent.click(screen.getByText('Prompt')); + expect( screen.getByText('Review the current branch and summarize the state.'), ).toBeInTheDocument(); @@ -77,11 +90,12 @@ describe('AcpToolDetails', () => { expect(codeBlockSpy).not.toHaveBeenCalled(); }); - it('hides expanded details for subagent rows without a prompt', () => { - const { container } = render( + it('shows the most recent subagent message when no prompt is available', () => { + render( { />, ); - expect(container).toBeEmptyDOMElement(); + expect( + screen.getByText('Found the requested implementation detail.'), + ).toBeInTheDocument(); + expect(screen.queryByText('Prompt')).not.toBeInTheDocument(); expect(toolInputSpy).not.toHaveBeenCalled(); expect(codeBlockSpy).not.toHaveBeenCalled(); }); + it('shows the latest live child message while a subagent is running', () => { + render( + )} + />, + ); + + expect( + screen.getByText('The child agent is reviewing the render path.'), + ).toBeInTheDocument(); + expect(screen.getByText('Prompt')).toBeInTheDocument(); + }); + + it('leaves the launch prompt open when the subagent has no message yet', () => { + render( + , + ); + + expect( + screen.getByText('Inspect the task transcript implementation.'), + ).toBeInTheDocument(); + }); + it('shows the launch prompt from rawInput for OpenCode task rows', () => { render( { expect(toolDetailsSpy).toHaveBeenCalled(); }); - it('does not render returned child text for completed subagent rows', () => { + it('expands completed subagent rows when a returned message is available', () => { render( { />, ); - expect( - screen.queryByText('Found the issue and confirmed the failing path.'), - ).not.toBeInTheDocument(); - expect(toolDetailsSpy).not.toHaveBeenCalled(); + expect(toolHeaderSpy).toHaveBeenCalledWith( + expect.objectContaining({ + collapsible: true, + }), + ); + expect(toolDetailsSpy).toHaveBeenCalled(); }); it('shows subagent payload details when debug visibility is enabled', () => { @@ -601,7 +603,7 @@ describe('AcpToolMessage', () => { }); }); - it('keeps subagent results without session artifacts as plain rows', () => { + it('expands subagent results without session artifacts to show their last message', () => { render( { ).not.toBeInTheDocument(); expect(toolHeaderSpy).toHaveBeenCalledWith( expect.objectContaining({ - collapsible: false, + collapsible: true, }), ); + expect(toolDetailsSpy).toHaveBeenCalled(); }); it('opens the artifact detail from the upload viewUrl when session path is missing', () => { diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/tool-detail-visibility.ts b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/tool-detail-visibility.ts index 478f53b32..4af75fca0 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/tool-detail-visibility.ts +++ b/apps/web/src/app/(sandbox)/task/[taskId]/messages/acp/tool-detail-visibility.ts @@ -43,6 +43,28 @@ export function getSubagentPrompt(msg: AcpToolUiMessage): string | null { return asNonEmptyString((rawInput as Record).prompt); } +export function getSubagentLastMessage(msg: AcpToolUiMessage): string | null { + if (!isSubagentToolPayload(msg.data)) { + return null; + } + + const output = + msg.kind === 'tool_result' ? asNonEmptyString(msg.data.output) : null; + + if (output) { + return output; + } + + const activity = (msg.data as unknown as Record) + .subagentActivity; + + if (!activity || typeof activity !== 'object' || Array.isArray(activity)) { + return null; + } + + return asNonEmptyString((activity as Record).lastMessage); +} + export function hidesExpandedToolResult( msg: AcpToolUiMessage, options?: ToolDetailVisibilityOptions, @@ -54,7 +76,9 @@ export function hidesExpandedToolResult( return false; } - return getSubagentPrompt(msg) === null; + return ( + getSubagentPrompt(msg) === null && getSubagentLastMessage(msg) === null + ); } return ( diff --git a/apps/web/src/components/settings/AdditionalEnvFieldInput.tsx b/apps/web/src/components/settings/AdditionalEnvFieldInput.tsx new file mode 100644 index 000000000..cd17fef62 --- /dev/null +++ b/apps/web/src/components/settings/AdditionalEnvFieldInput.tsx @@ -0,0 +1,72 @@ +'use client'; + +import type { SetupModelProviderEnvField } from '@roomote/types'; + +import { + Input, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/system'; + +/** + * One additional provider env field on a connect surface: a select when the + * field declares options, a text input otherwise. Shared by the settings + * dialog and the onboarding step so both render a field the same way. + */ +export function AdditionalEnvFieldInput({ + field, + value, + onValueChange, + disabled, + ariaLabel, + inputClassName, + selectTriggerClassName, +}: { + field: SetupModelProviderEnvField; + value: string; + onValueChange: (value: string) => void; + disabled: boolean; + ariaLabel: string; + inputClassName?: string; + selectTriggerClassName?: string; +}) { + if (field.options && field.options.length > 0) { + return ( + + ); + } + + return ( + onValueChange(event.target.value)} + placeholder={field.placeholder} + disabled={disabled} + aria-label={ariaLabel} + data-1p-ignore + /> + ); +} diff --git a/apps/web/src/components/settings/InferenceProviderSection.test.tsx b/apps/web/src/components/settings/InferenceProviderSection.test.tsx index 44b8dac85..223984df1 100644 --- a/apps/web/src/components/settings/InferenceProviderSection.test.tsx +++ b/apps/web/src/components/settings/InferenceProviderSection.test.tsx @@ -692,6 +692,61 @@ describe('InferenceProviderSection', () => { }); }); + it('submits the first option for an untouched selectable field', async () => { + const { providerSetup } = buildProviderSetup(); + providerSetup.providers = [ + { + id: 'zai' as SetupModelProviderId, + label: 'Z.AI', + envVarName: 'ZAI_API_KEY', + defaultRoomoteModel: 'zai/glm-5.2', + authKind: 'api-key', + suggestedTaskModels: [], + additionalEnvFields: [ + { + envVarName: 'ZAI_REGION', + label: 'Region', + secret: false, + required: true, + options: [ + { value: 'global', label: 'International' }, + { value: 'china', label: 'China' }, + ], + }, + ], + runtimeApiKeySatisfied: false, + savedApiKeySatisfied: false, + additionalEnvValues: {}, + }, + ]; + providerSetupData.current = { providerSetup }; + mutateAsyncMock.mockResolvedValue({}); + + renderInferenceProviderSection(); + + fireEvent.click(screen.getByRole('button', { name: /Add provider/ })); + + // A field with options renders as a select showing its first option, and + // saving must submit that value rather than the empty string the user + // never typed into. + expect( + screen.getByRole('combobox', { name: 'Region for Z.AI' }), + ).toHaveTextContent('International'); + + await act(async () => { + fireEvent.change(screen.getByLabelText('API key for Z.AI'), { + target: { value: 'zai-key' }, + }); + fireEvent.click(screen.getByRole('button', { name: 'Add' })); + }); + + expect(mutateAsyncMock).toHaveBeenCalledWith({ + provider: 'zai', + apiKey: 'zai-key', + additionalEnvValues: { ZAI_REGION: 'global' }, + }); + }); + it('closes the endpoint dialog after connecting', async () => { const { providerSetup } = buildProviderSetup(); providerSetup.providers = [ diff --git a/apps/web/src/components/settings/InferenceProviderSection.tsx b/apps/web/src/components/settings/InferenceProviderSection.tsx index cee52a6d7..9385a7012 100644 --- a/apps/web/src/components/settings/InferenceProviderSection.tsx +++ b/apps/web/src/components/settings/InferenceProviderSection.tsx @@ -6,6 +6,7 @@ import { toast } from 'sonner'; import { CHATGPT_SUBSCRIPTION_PROVIDER_ID, OPENAI_COMPATIBLE_PROVIDER_ID, + getDefaultAdditionalEnvValues, getModelProviderLabel, } from '@roomote/types'; import type { @@ -44,6 +45,7 @@ import { Trash2, } from '@/components/system'; import { Section } from '@/components/settings/Section'; +import { AdditionalEnvFieldInput } from '@/components/settings/AdditionalEnvFieldInput'; import { ChatGptConnectDialog } from '@/components/settings/ChatGptConnectDialog'; import { GitHubCopilotConnectDialog } from '@/components/settings/GitHubCopilotConnectDialog'; import { ProviderCreditBalanceLine } from '@/components/settings/ProviderCreditBalanceLine'; @@ -82,11 +84,16 @@ function getInitialAdditionalEnvValues( (provider.additionalEnvFields ?? []).map((field) => field.envVarName), ); - return Object.fromEntries( + const values = Object.fromEntries( Object.entries(provider.additionalEnvValues ?? {}).filter(([name]) => declaredNames.has(name), ), ); + + return getDefaultAdditionalEnvValues( + provider.additionalEnvFields ?? [], + values, + ); } function getInitialPrimaryCredential( @@ -112,11 +119,13 @@ function getSubmitAdditionalEnvValues( additionalEnvFields.map((field) => field.envVarName), ); - return Object.fromEntries( + const values = Object.fromEntries( Object.entries(additionalEnvValues).filter(([name]) => declaredNames.has(name), ), ); + + return getDefaultAdditionalEnvValues(additionalEnvFields, values); } function ConnectedProviderRow({ @@ -485,20 +494,18 @@ function ProviderCredentialsDialog({ {field.label} {field.required ? '' : ' (optional)'} - + onValueChange={(value) => setAdditionalEnvValues((values) => ({ ...values, - [field.envVarName]: event.target.value, + [field.envVarName]: value, })) } - placeholder={field.placeholder} disabled={isSaving} - aria-label={`${field.label} for ${selectedProvider.label}`} - data-1p-ignore + ariaLabel={`${field.label} for ${selectedProvider.label}`} + inputClassName={field.secret ? 'font-mono' : undefined} /> ))} diff --git a/apps/web/src/trpc/commands/task-models/index.test.ts b/apps/web/src/trpc/commands/task-models/index.test.ts index 6c292b8be..87c580dea 100644 --- a/apps/web/src/trpc/commands/task-models/index.test.ts +++ b/apps/web/src/trpc/commands/task-models/index.test.ts @@ -85,6 +85,10 @@ const PROVIDER_ENV_VAR_NAMES = [ 'MOONSHOT_API_KEY', 'KIMI_API_KEY', 'MINIMAX_API_KEY', + 'ZAI_API_KEY', + 'ZAI_REGION', + 'ZAI_CODING_PLAN_API_KEY', + 'ZAI_CODING_PLAN_REGION', 'OPENCODE_API_KEY', 'AWS_BEARER_TOKEN_BEDROCK', 'AWS_REGION', @@ -1342,6 +1346,8 @@ describe('task model provider commands', () => { expect(mockGetPersistedEnvironmentVariableValues).toHaveBeenCalledWith([ 'AWS_REGION', + 'ZAI_REGION', + 'ZAI_CODING_PLAN_REGION', 'OPENAI_COMPATIBLE_BASE_URL', 'LITELLM_BASE_URL', 'OLLAMA_BASE_URL', diff --git a/apps/worker/Dockerfile b/apps/worker/Dockerfile index 513750719..abf2c928a 100644 --- a/apps/worker/Dockerfile +++ b/apps/worker/Dockerfile @@ -66,8 +66,6 @@ RUN apt-get update \ libxrandr2 \ libxss1 \ && ln -sf "$(command -v python3)" /usr/local/bin/python \ - && update-alternatives --set iptables /usr/sbin/iptables-legacy \ - && update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy \ && docker compose version \ && rm -rf /var/lib/apt/lists/* diff --git a/apps/worker/package.json b/apps/worker/package.json index add1b5c8c..e293e6817 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -18,7 +18,7 @@ "internal-deps": "pnpm list --filter @roomote/worker --depth Infinity 2>/dev/null | grep -E '@roomote/' | grep 'link:' | sed 's/.*@roomote/@roomote/' | sed 's/ link:.*//' | sort -u" }, "dependencies": { - "@hono/node-server": "1.19.13", + "@hono/node-server": "2.0.5", "@modelcontextprotocol/sdk": "^1.29.0", "@roomote/auth": "workspace:^", "@roomote/cloud-agents": "workspace:^", @@ -33,7 +33,7 @@ "@trpc/server": "^11.15.0", "chokidar": "^4.0.3", "commander": "^14.0.2", - "dompurify": "3.4.11", + "dompurify": "3.4.12", "execa": "9.6.1", "hono": "4.12.27", "http-proxy": "^1.18.1", diff --git a/apps/worker/src/run-task/__tests__/run-task.test.ts b/apps/worker/src/run-task/__tests__/run-task.test.ts index b74dd7032..dc7082df5 100644 --- a/apps/worker/src/run-task/__tests__/run-task.test.ts +++ b/apps/worker/src/run-task/__tests__/run-task.test.ts @@ -1383,6 +1383,63 @@ describe('runTask', () => { ); }); + it('keeps scheduled automation scans silent without requiring a closeout', async () => { + const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(456_000); + + try { + await runTask({ + taskRun: { + id: 108, + taskId: 'task-108', + payloadKind: TaskPayloadKind.Scan, + harness: 'opencode-server', + payload: { + repo: '__all_repositories__', + slackChannel: 'C123', + channel: 'C123', + suggestionSource: 'codeql_triage', + }, + result: null, + } as never, + envVars: {}, + workspacePath: '/tmp/workspace', + prompt: '', + harnessInstructions: undefined, + agentInstructions: undefined, + environmentConfig: undefined, + callbacks: {}, + context: {}, + logger: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + log: vi.fn(), + } as never, + harnessSessionId: 'session-108', + workerEnv: { + authToken: 'cloud-token', + roomoteAppUrl: 'https://api.example.test', + trpcUrl: 'https://web.example.test', + buildUserFacingEnv: vi.fn(() => ({ + HOME: '/tmp/home', + PATH: '/usr/bin', + })), + } as never, + }); + } finally { + nowSpy.mockRestore(); + } + + expect(writeFileSyncMock).toHaveBeenCalledWith( + '/tmp/workspace/.roomote-runtime-home/.config/opencode/roomote-slack-reply-satisfaction.json', + JSON.stringify({ + startedAtMs: 456_000, + currentTurnRequiresInitialAck: false, + }), + 'utf8', + ); + }); + it('marks Slack custom automation runs as silent with a required terminal closeout', async () => { const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(456_789); diff --git a/apps/worker/src/run-task/run-task.ts b/apps/worker/src/run-task/run-task.ts index 7ddb9ad24..009c68197 100644 --- a/apps/worker/src/run-task/run-task.ts +++ b/apps/worker/src/run-task/run-task.ts @@ -239,14 +239,38 @@ function hasCustomAutomationId(taskRun: { payload: unknown }): boolean { ); } +function hasScheduledAutomationSource(taskRun: { payload: unknown }): boolean { + if (!taskRun.payload || typeof taskRun.payload !== 'object') { + return false; + } + + const payload = taskRun.payload as { + suggestionSource?: unknown; + }; + + return ( + typeof payload.suggestionSource === 'string' && + payload.suggestionSource.trim().length > 0 + ); +} + /** - * Channel-only automation launches (work-item execution tasks and custom - * automation runs) stay silent until they have a final result or blocker; - * their first chat message late-binds the report thread, so an opening ack - * would hijack the thread root. + * Channel-only automation launches stay silent until they have a result or + * blocker. Scheduled scan tasks have no inbound message to acknowledge, and + * execution tasks late-bind their report thread on the first chat message. */ function isSilentChannelAutomationLaunch(taskRun: { payload: unknown; +}): boolean { + return ( + hasAutomationWorkItemId(taskRun) || + hasCustomAutomationId(taskRun) || + hasScheduledAutomationSource(taskRun) + ); +} + +function requiresLateBoundAutomationCloseout(taskRun: { + payload: unknown; }): boolean { return hasAutomationWorkItemId(taskRun) || hasCustomAutomationId(taskRun); } @@ -897,7 +921,8 @@ export const runTask = async ({ // custom automation runs) have no inbound Slack turn, but must // still end with one agent-written closeout; the Stop hook blocks // silent completion when this flag is set. - ...(!initialTurnMessageTs && isSilentChannelAutomationLaunch(taskRun) + ...(!initialTurnMessageTs && + requiresLateBoundAutomationCloseout(taskRun) ? { requiresTerminalCloseoutWithoutTurn: true } : {}), }), diff --git a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-subagent-watchdog.test.ts b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-subagent-watchdog.test.ts index 1f55333b0..c28d8297a 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-subagent-watchdog.test.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-subagent-watchdog.test.ts @@ -166,6 +166,17 @@ function createChildToolPart(options: { }; } +function createChildTextPart(text: string, role?: 'assistant') { + return { + id: 'prt_child_text_1', + sessionID: 'ses_child_1', + messageID: 'msg_child_1', + type: 'text', + text, + ...(role ? { role } : {}), + }; +} + async function armSpawn( client: FakeOpenCodeServerClient, harness: OpenCodeServerHarness, @@ -880,6 +891,206 @@ describe('OpenCode subagent live activity', () => { } }); + it('folds child assistant text into the subagent activity update before message metadata arrives', async () => { + const { client, harness } = createHarness(); + const outputs: Array> = []; + harness.on('runtimeOutput', (event) => { + outputs.push(event as unknown as Record); + }); + + try { + await connectHarness(harness, client); + await armSpawn(client, harness); + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart('The latest child response.', 'assistant'), + }, + }); + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart('The final child response.', 'assistant'), + }, + }); + + const activity = subagentActivityEvents(outputs); + expect(activity).toHaveLength(1); + const details = (activity[0]!.payload as Record) + .subagentActivity as Record; + expect(details.lastMessage).toBe('The latest child response.'); + } finally { + await harness.dispose(); + } + }); + + it('throttles streamed child text instead of emitting once per token', async () => { + const { client, harness } = createHarness(); + const outputs: Array> = []; + harness.on('runtimeOutput', (event) => { + outputs.push(event as unknown as Record); + }); + + try { + await connectHarness(harness, client); + vi.useFakeTimers(); + await armSpawn(client, harness); + + // Text parts carry the full accumulated message, so a streamed response + // arrives as a growing prefix on every token. + for (const text of ['The', 'The final', 'The final child response.']) { + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart(text, 'assistant'), + }, + }); + } + + const activity = subagentActivityEvents(outputs); + expect(activity).toHaveLength(1); + const details = (activity[0]!.payload as Record) + .subagentActivity as Record; + expect(details.lastMessage).toBe('The'); + } finally { + await harness.dispose(); + } + }); + + it('flushes the newest child text once the throttle window closes', async () => { + const { client, harness } = createHarness(); + const outputs: Array> = []; + harness.on('runtimeOutput', (event) => { + outputs.push(event as unknown as Record); + }); + + try { + await connectHarness(harness, client); + vi.useFakeTimers(); + await armSpawn(client, harness); + + for (const text of ['The', 'The final', 'The final child response.']) { + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart(text, 'assistant'), + }, + }); + } + + expect(subagentActivityEvents(outputs)).toHaveLength(1); + + // Nothing further arrives from the child, so only the trailing edge can + // carry the newest text to the transcript. + await vi.advanceTimersByTimeAsync(6_000); + + const activity = subagentActivityEvents(outputs); + expect(activity).toHaveLength(2); + const details = (activity[1]!.payload as Record) + .subagentActivity as Record; + expect(details.lastMessage).toBe('The final child response.'); + } finally { + await harness.dispose(); + } + }); + + it('drops a pending activity flush when the spawn settles', async () => { + const { client, harness } = createHarness(); + const outputs: Array> = []; + harness.on('runtimeOutput', (event) => { + outputs.push(event as unknown as Record); + }); + + try { + await connectHarness(harness, client); + vi.useFakeTimers(); + await armSpawn(client, harness); + + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart('Working on it.', 'assistant'), + }, + }); + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart('Working on it still.', 'assistant'), + }, + }); + + expect(subagentActivityEvents(outputs)).toHaveLength(1); + + await client.emit({ + type: 'message.part.updated', + properties: { + part: createTaskToolPart({ + status: 'completed', + metadata: { sessionId: 'ses_child_1' }, + }), + }, + }); + + // A flush firing after settlement would emit `running: true` and flip the + // settled row back to in progress. + await vi.advanceTimersByTimeAsync(30_000); + + expect(subagentActivityEvents(outputs)).toHaveLength(1); + } finally { + await harness.dispose(); + } + }); + + it('keeps the last child message in terminal activity when task output is empty', async () => { + const { client, harness } = createHarness(); + const outputs: Array> = []; + harness.on('runtimeOutput', (event) => { + outputs.push(event as unknown as Record); + }); + + try { + await connectHarness(harness, client); + await armSpawn(client, harness); + await client.emit({ + type: 'message.updated', + properties: { + info: { + id: 'msg_child_1', + sessionID: 'ses_child_1', + role: 'assistant', + time: { created: 1 }, + }, + }, + }); + await client.emit({ + type: 'message.part.updated', + properties: { + part: createChildTextPart('The child response to preserve.'), + }, + }); + await client.emit({ + type: 'message.part.updated', + properties: { + part: createTaskToolPart({ + status: 'completed', + metadata: { sessionId: 'ses_child_1' }, + }), + }, + }); + + const terminal = outputs.find((event) => { + const payload = event.payload as Record; + return payload.status === 'completed'; + }); + const activity = (terminal?.payload as Record) + .subagentActivity as Record; + + expect(activity.lastMessage).toBe('The child response to preserve.'); + } finally { + await harness.dispose(); + } + }); + it('throttles activity emissions and keeps counting child tool calls', async () => { const { client, harness } = createHarness(); const outputs: Array> = []; @@ -906,17 +1117,21 @@ describe('OpenCode subagent live activity', () => { expect(subagentActivityEvents(outputs)).toHaveLength(1); - vi.advanceTimersByTime(6_000); + // The trailing edge delivers the call the throttle swallowed. + await vi.advanceTimersByTimeAsync(6_000); + expect(subagentActivityEvents(outputs)).toHaveLength(2); + await client.emit({ type: 'message.part.updated', properties: { part: createChildToolPart({ callId: 'c3', command: 'date' }), }, }); + await vi.advanceTimersByTimeAsync(6_000); const activity = subagentActivityEvents(outputs); - expect(activity).toHaveLength(2); - const details = (activity[1]!.payload as Record) + expect(activity).toHaveLength(3); + const details = (activity[2]!.payload as Record) .subagentActivity as Record; expect(details.toolCallCount).toBe(3); } finally { diff --git a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts index 7950ef822..3bd6c8aac 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts @@ -264,7 +264,12 @@ interface ActiveOpenCodeSubagentWatchdog { updatePayload: Record; activitySeenChildToolCallIds: Set; activityLastAction: string | null; + activityLastMessage: string | null; + childAssistantMessageIds: Set; activityLastEmitAtMs: number; + // Armed when an activity change lands inside the throttle window, so the + // newest action and message still reach the transcript once it closes. + activityFlushTimer: ReturnType | null; } const OPEN_CODE_EXECUTE_TOOLS = new Set(['bash', 'shell']); @@ -2484,7 +2489,10 @@ export class OpenCodeServerHarness updatePayload: input.updatePayload, activitySeenChildToolCallIds: new Set(), activityLastAction: null, + activityLastMessage: null, + childAssistantMessageIds: new Set(), activityLastEmitAtMs: 0, + activityFlushTimer: null, }; this.activeSubagentWatchdogs.set(eventKey, watchdog); if (input.childSessionId) { @@ -2566,6 +2574,7 @@ export class OpenCodeServerHarness return { agentType: watchdog.agentType, lastAction: watchdog.activityLastAction, + lastMessage: watchdog.activityLastMessage, toolCallCount: watchdog.activitySeenChildToolCallIds.size, startedAtMs: watchdog.startedAtMs, elapsedMs: Date.now() - watchdog.startedAtMs, @@ -2583,12 +2592,27 @@ export class OpenCodeServerHarness if (watchdog.settlementTimer) { clearTimeout(watchdog.settlementTimer); } + this.clearSubagentActivityFlush(watchdog); if (watchdog.childSessionId) { this.childSessionWatchdogKeys.delete(watchdog.childSessionId); } this.activeSubagentWatchdogs.delete(eventKey); } + /** + * A pending flush must never outlive its watchdog: it emits an in_progress + * update, so firing after the spawn settles would flip the transcript row + * back to running. + */ + private clearSubagentActivityFlush( + watchdog: ActiveOpenCodeSubagentWatchdog, + ): void { + if (watchdog.activityFlushTimer) { + clearTimeout(watchdog.activityFlushTimer); + watchdog.activityFlushTimer = null; + } + } + private clearAllSubagentWatchdogs(options?: { keepBackgroundWatchdogs?: boolean; }): void { @@ -2600,6 +2624,7 @@ export class OpenCodeServerHarness if (watchdog.settlementTimer) { clearTimeout(watchdog.settlementTimer); } + this.clearSubagentActivityFlush(watchdog); this.activeSubagentWatchdogs.delete(eventKey); if (watchdog.childSessionId) { this.childSessionWatchdogKeys.delete(watchdog.childSessionId); @@ -2768,7 +2793,48 @@ export class OpenCodeServerHarness const part = asRecord(asRecord(payload.properties)?.part); - if (!part || asString(part.type) !== 'tool') { + if (!part) { + return; + } + + const partType = asString(part.type); + + if (partType === 'text') { + const messageId = asString(part.messageID); + const messageRole = + parseOpenCodeMessageRole(part.role) ?? + extractOpenCodePartMessageRole( + asRecord(payload.properties), + part as OpenCodePart, + messageId ?? undefined, + ); + + if ( + !messageId || + (messageRole !== 'assistant' && + !watchdog.childAssistantMessageIds.has(messageId)) + ) { + return; + } + + watchdog.childAssistantMessageIds.add(messageId); + + const message = asString(part.text)?.trim(); + + if (!message || message === watchdog.activityLastMessage) { + return; + } + + // Text parts carry the whole accumulated message, not a delta, so this + // fires once per streamed token. Record every one but let the shared + // throttle decide when it reaches the transcript; the terminal snapshot + // reads the stored value, so nothing is lost by waiting. + watchdog.activityLastMessage = message; + this.emitSubagentActivityUpdate(watchdog); + return; + } + + if (partType !== 'tool') { return; } @@ -2796,15 +2862,38 @@ export class OpenCodeServerHarness watchdog.activityLastAction = action; } - const nowMs = Date.now(); + this.emitSubagentActivityUpdate(watchdog); + } + + private emitSubagentActivityUpdate( + watchdog: ActiveOpenCodeSubagentWatchdog, + ): void { + const sinceLastEmitMs = Date.now() - watchdog.activityLastEmitAtMs; + + if (sinceLastEmitMs < SUBAGENT_ACTIVITY_EMIT_INTERVAL_MS) { + // A change the throttle swallowed would otherwise never surface if it is + // the last one before the child goes quiet, so arm the trailing edge. + if (!watchdog.activityFlushTimer) { + const timer = setTimeout(() => { + watchdog.activityFlushTimer = null; + this.publishSubagentActivityUpdate(watchdog); + }, SUBAGENT_ACTIVITY_EMIT_INTERVAL_MS - sinceLastEmitMs); + timer.unref?.(); + watchdog.activityFlushTimer = timer; + } - if ( - nowMs - watchdog.activityLastEmitAtMs < - SUBAGENT_ACTIVITY_EMIT_INTERVAL_MS - ) { return; } + this.publishSubagentActivityUpdate(watchdog); + } + + private publishSubagentActivityUpdate( + watchdog: ActiveOpenCodeSubagentWatchdog, + ): void { + this.clearSubagentActivityFlush(watchdog); + + const nowMs = Date.now(); watchdog.activityLastEmitAtMs = nowMs; this.runtimeEvents.toolUpdate({ sessionId: watchdog.sessionId, @@ -2820,6 +2909,7 @@ export class OpenCodeServerHarness subagentActivity: { agentType: watchdog.agentType, lastAction: watchdog.activityLastAction, + lastMessage: watchdog.activityLastMessage, toolCallCount: watchdog.activitySeenChildToolCallIds.size, startedAtMs: watchdog.startedAtMs, elapsedMs: nowMs - watchdog.startedAtMs, @@ -2855,6 +2945,13 @@ export class OpenCodeServerHarness return; } + const eventKey = this.childSessionWatchdogKeys.get(childSessionId); + const watchdog = eventKey + ? this.activeSubagentWatchdogs.get(eventKey) + : undefined; + + watchdog?.childAssistantMessageIds.add(info.id); + if (!info.time?.completed) { return; } diff --git a/deploy/caddy/Caddyfile b/deploy/caddy/Caddyfile index e3bdc3542..9fad68d53 100644 --- a/deploy/caddy/Caddyfile +++ b/deploy/caddy/Caddyfile @@ -1,7 +1,6 @@ { - on_demand_tls { - ask http://web:3000/api/caddy/ask - } + {$ROOMOTE_CADDY_LOCAL_CERTS:} + {$ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET:import roomote_on_demand_tls} } (roomote_proxy) { @@ -12,6 +11,18 @@ } } +(roomote_on_demand_tls) { + on_demand_tls { + ask http://web:3000/api/caddy/ask + } +} + +(roomote_on_demand_wildcard_tls) { + tls { + on_demand + } +} + {$ROOMOTE_APP_DOMAIN} { @artifact_storage { path /{$S3_BUCKET_ARTIFACTS:roomote-artifacts} /{$S3_BUCKET_ARTIFACTS:roomote-artifacts}/* @@ -43,9 +54,7 @@ } *.{$ROOMOTE_PREVIEW_DOMAIN} { - tls { - on_demand - } + {$ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET:import roomote_on_demand_wildcard_tls} import roomote_proxy preview-proxy:8081 } diff --git a/deploy/ci/validate-deployment-artifacts.mjs b/deploy/ci/validate-deployment-artifacts.mjs index 85f6e5f08..88a357012 100644 --- a/deploy/ci/validate-deployment-artifacts.mjs +++ b/deploy/ci/validate-deployment-artifacts.mjs @@ -62,6 +62,9 @@ const composeEnv = { PREVIEW_AUTH_PUBLIC_KEY: 'deployment-ci-preview-public-key', REDIS_URL: 'redis://redis:6379', ROOMOTE_APP_DOMAIN: 'roomote.localhost', + ROOMOTE_CADDY_LOCAL_CERTS: 'local_certs', + ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET: '', + ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET: '', R_APP_URL: 'http://roomote.localhost', ROOMOTE_PREVIEW_DOMAIN: 'preview.roomote.localhost', ROOMOTE_VERSION: 'deployment-ci', @@ -140,8 +143,7 @@ function validateComposeShape(shape) { // Coolify uses platform magic vars that compose does not interpolate here. if ( !shape.coolify && - 'R_DISCORD_GATEWAY_SECRET' in - (config.services.bullmq.environment ?? {}) + 'R_DISCORD_GATEWAY_SECRET' in (config.services.bullmq.environment ?? {}) ) { assert( config.services.bullmq.environment?.R_DISCORD_GATEWAY_SECRET === @@ -151,6 +153,27 @@ function validateComposeShape(shape) { } } + if ( + 'ROOMOTE_CADDY_LOCAL_CERTS' in (config.services.caddy?.environment ?? {}) + ) { + assert( + config.services.caddy.environment?.ROOMOTE_CADDY_LOCAL_CERTS === + composeEnv.ROOMOTE_CADDY_LOCAL_CERTS, + `${shape.name}: caddy must receive ROOMOTE_CADDY_LOCAL_CERTS`, + ); + assert( + config.services.caddy.environment?.ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET === + composeEnv.ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET, + `${shape.name}: caddy must receive ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET`, + ); + assert( + config.services.caddy.environment + ?.ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET === + composeEnv.ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET, + `${shape.name}: caddy must receive ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET`, + ); + } + console.log(`validated compose shape: ${shape.name}`); } finally { rmSync(temporaryDirectory, { recursive: true, force: true }); @@ -239,6 +262,65 @@ assert( const productionCompose = YAML.parse( read('deploy/compose/docker-compose.prod.yml'), ); +assert( + read('deploy/caddy/Caddyfile').includes('{$ROOMOTE_CADDY_LOCAL_CERTS:}'), + 'caddy: Caddyfile must support the installer-managed local certificate mode', +); +assert( + read('deploy/caddy/Caddyfile').includes( + '{$ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET:import roomote_on_demand_tls}', + ), + 'caddy: Caddyfile must allow internal mode to remove global on-demand TLS', +); +assert( + read('deploy/caddy/Caddyfile').includes( + '{$ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET:import roomote_on_demand_wildcard_tls}', + ), + 'caddy: Caddyfile must allow internal mode to remove wildcard on-demand TLS', +); +const caddyfile = read('deploy/caddy/Caddyfile'); +const renderCaddyTlsMode = (values) => + caddyfile + .replace('{$ROOMOTE_CADDY_LOCAL_CERTS:}', values.localCertificates) + .replace( + '{$ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET:import roomote_on_demand_tls}', + values.globalTlsSnippet, + ) + .replace( + '{$ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET:import roomote_on_demand_wildcard_tls}', + values.wildcardTlsSnippet, + ); +const acmeCaddyfile = renderCaddyTlsMode({ + localCertificates: '', + globalTlsSnippet: 'import roomote_on_demand_tls', + wildcardTlsSnippet: 'import roomote_on_demand_wildcard_tls', +}); +const internalCaddyfile = renderCaddyTlsMode({ + localCertificates: 'local_certs', + globalTlsSnippet: '', + wildcardTlsSnippet: '', +}); +const caddyGlobalBlock = (contents) => + contents.slice(0, contents.indexOf('}\n')); +const caddyWildcardSite = (contents) => + contents.slice(contents.indexOf('*.{$ROOMOTE_PREVIEW_DOMAIN} {')); +assert( + caddyGlobalBlock(acmeCaddyfile).includes('import roomote_on_demand_tls') && + caddyWildcardSite(acmeCaddyfile).includes( + 'import roomote_on_demand_wildcard_tls', + ), + 'caddy: acme mode must import global and wildcard on-demand TLS', +); +assert( + caddyGlobalBlock(internalCaddyfile).includes('local_certs') && + !caddyGlobalBlock(internalCaddyfile).includes( + 'import roomote_on_demand_tls', + ) && + !caddyWildcardSite(internalCaddyfile).includes( + 'import roomote_on_demand_wildcard_tls', + ), + 'caddy: internal mode must use local certificates without on-demand TLS', +); const coolify = YAML.parse(read('deploy/coolify/docker-compose.yaml')); assert( read('deploy/coolify/docker-compose.yaml').includes( diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml index e6c90e351..daaa1f830 100644 --- a/deploy/compose/docker-compose.prod.yml +++ b/deploy/compose/docker-compose.prod.yml @@ -43,6 +43,10 @@ x-roomote-inference-env: &roomote-inference-env MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-} KIMI_API_KEY: ${KIMI_API_KEY:-} MINIMAX_API_KEY: ${MINIMAX_API_KEY:-} + ZAI_API_KEY: ${ZAI_API_KEY:-} + ZAI_REGION: ${ZAI_REGION:-} + ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-} + ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-} OPENCODE_API_KEY: ${OPENCODE_API_KEY:-} BASETEN_API_KEY: ${BASETEN_API_KEY:-} TOGETHER_API_KEY: ${TOGETHER_API_KEY:-} @@ -496,6 +500,9 @@ services: environment: ROOMOTE_APP_DOMAIN: ${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required} ROOMOTE_PREVIEW_DOMAIN: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required} + ROOMOTE_CADDY_LOCAL_CERTS: ${ROOMOTE_CADDY_LOCAL_CERTS:-} + ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET: ${ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET-import roomote_on_demand_tls} + ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET: ${ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET-import roomote_on_demand_wildcard_tls} S3_BUCKET_ARTIFACTS: ${S3_BUCKET_ARTIFACTS:-roomote-artifacts} ports: - '${CADDY_HTTP_PORT:-80}:80' diff --git a/deploy/install.sh b/deploy/install.sh index 54184270e..7e0e7c13c 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -35,6 +35,8 @@ usage: install.sh [options] Options: --domain Public app hostname (default: roomote..sslip.io) --preview-domain Preview hostname (default: preview.) + --tls-mode TLS certificate mode (default: acme) + --skip-dns-check Skip public-DNS verification for --domain --version Roomote image tag (default: latest GitHub release) --repo GitHub repo for compose/Caddyfile fetch (default: RooCodeInc/Roomote) @@ -44,6 +46,7 @@ Options: Environment overrides: ROOMOTE_VERSION + ROOMOTE_TLS_MODE Certificate mode: acme or internal ROOMOTE_FETCH_BASE Mirror for the release lookup and deployment file fetches (default https://get.roomote.dev for the official repo; GitHub is the fallback) @@ -78,6 +81,8 @@ die() { domain='' preview_domain='' +tls_mode="${ROOMOTE_TLS_MODE:-}" +skip_dns_check='false' roomote_version="${ROOMOTE_VERSION:-}" repo='RooCodeInc/Roomote' image_registry='ghcr.io' @@ -95,6 +100,14 @@ while [ "$#" -gt 0 ]; do preview_domain="${2:-}" shift 2 ;; + --tls-mode) + tls_mode="${2:-}" + shift 2 + ;; + --skip-dns-check) + skip_dns_check='true' + shift + ;; --version) roomote_version="${2:-}" shift 2 @@ -297,8 +310,6 @@ detect_public_ip() { return 1 } -public_ip="$(detect_public_ip)" || public_ip='' - if [ -z "$domain" ] && [ -f "$install_root/.env" ]; then domain="$(awk -F= '/^ROOMOTE_APP_DOMAIN=/ { print $2; exit }' "$install_root/.env")" if [ -n "$domain" ]; then @@ -306,12 +317,31 @@ if [ -z "$domain" ] && [ -f "$install_root/.env" ]; then fi fi +if [ -z "$tls_mode" ] && [ -f "$install_root/.env" ]; then + tls_mode="$(awk -F= '/^ROOMOTE_TLS_MODE=/ { print $2; exit }' "$install_root/.env")" +fi +tls_mode="${tls_mode:-acme}" +case "$tls_mode" in + acme | internal) ;; + *) die "--tls-mode must be acme or internal" ;; +esac + +public_ip='' if [ -z "$domain" ]; then + if [ "$tls_mode" = 'internal' ]; then + die "--tls-mode internal requires --domain " + fi + public_ip="$(detect_public_ip)" || public_ip='' [ -n "$public_ip" ] || die "could not detect this host's public IPv4 address; pass --domain instead" domain="roomote.$(printf '%s' "$public_ip" | tr '.' '-').sslip.io" log "No --domain given; using the zero-DNS default $domain" warn "sslip.io domains share Let's Encrypt rate limits and are tied to this IP. Use --domain for production." else + if [ "$skip_dns_check" = 'true' ] || [ "$tls_mode" = 'internal' ]; then + log "Skipping public DNS verification for $domain" + else + public_ip="$(detect_public_ip)" || public_ip='' + fi if [ -n "$public_ip" ]; then log "Checking that $domain resolves to $public_ip" resolved='' @@ -519,6 +549,16 @@ set_env_value ROOMOTE_VERSION "$roomote_version" set_env_value ROOMOTE_REPO "$repo" set_env_value ROOMOTE_APP_DOMAIN "$domain" set_env_value ROOMOTE_PREVIEW_DOMAIN "$preview_domain" +set_env_value ROOMOTE_TLS_MODE "$tls_mode" +if [ "$tls_mode" = 'internal' ]; then + set_env_value ROOMOTE_CADDY_LOCAL_CERTS 'local_certs' + set_env_value ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET '' + set_env_value ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET '' +else + set_env_value ROOMOTE_CADDY_LOCAL_CERTS '' + set_env_value ROOMOTE_CADDY_GLOBAL_TLS_SNIPPET 'import roomote_on_demand_tls' + set_env_value ROOMOTE_CADDY_WILDCARD_TLS_SNIPPET 'import roomote_on_demand_wildcard_tls' +fi set_env_value TRPC_URL "https://$domain/_roomote-api" set_env_value IMAGE_REGISTRY "$image_registry" set_env_value IMAGE_NAMESPACE "$image_namespace" @@ -588,8 +628,8 @@ Roomote is up. One step left: https://$domain/setup?token=$setup_token -If the browser shows a TLS error, the certificate is still being issued -- -wait a minute and reload. +If the browser shows a TLS error in acme mode, the certificate is still being +issued -- wait a minute and reload. Manage later with the roomote command: roomote status | logs | upgrade | backup EOF diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 7d52ac339..bb4d76510 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -46,6 +46,10 @@ x-roomote-production-env: &roomote-production-env MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-} KIMI_API_KEY: ${KIMI_API_KEY:-} MINIMAX_API_KEY: ${MINIMAX_API_KEY:-} + ZAI_API_KEY: ${ZAI_API_KEY:-} + ZAI_REGION: ${ZAI_REGION:-} + ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-} + ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-} OPENCODE_API_KEY: ${OPENCODE_API_KEY:-} BASETEN_API_KEY: ${BASETEN_API_KEY:-} TOGETHER_API_KEY: ${TOGETHER_API_KEY:-} diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml index 070c8cced..99050bb97 100644 --- a/docker-compose.self-host.yml +++ b/docker-compose.self-host.yml @@ -44,6 +44,10 @@ x-roomote-env: &roomote-env MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-} KIMI_API_KEY: ${KIMI_API_KEY:-} MINIMAX_API_KEY: ${MINIMAX_API_KEY:-} + ZAI_API_KEY: ${ZAI_API_KEY:-} + ZAI_REGION: ${ZAI_REGION:-} + ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-} + ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-} OPENCODE_API_KEY: ${OPENCODE_API_KEY:-} BASETEN_API_KEY: ${BASETEN_API_KEY:-} TOGETHER_API_KEY: ${TOGETHER_API_KEY:-} diff --git a/ecosystem.config.js b/ecosystem.config.js index 6e915bb1f..190ae7c9b 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -9,6 +9,10 @@ const DEFAULT_OPENCODE_PROVIDER_ENV_KEYS = [ 'MOONSHOT_API_KEY', 'KIMI_API_KEY', 'MINIMAX_API_KEY', + 'ZAI_API_KEY', + 'ZAI_REGION', + 'ZAI_CODING_PLAN_API_KEY', + 'ZAI_CODING_PLAN_REGION', 'OPENCODE_API_KEY', 'BASETEN_API_KEY', 'TOGETHER_API_KEY', diff --git a/package.json b/package.json index 4b2c20ffb..d4a2f1785 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "roomote", - "version": "0.20.1", + "version": "0.21.0", "license": "FCL-1.0-ALv2", "packageManager": "pnpm@10.29.3", "engines": { @@ -96,7 +96,7 @@ "@vercel/routing-utils>ajv": "6.14.0", "@vercel/routing-utils>path-to-regexp": "6.3.0", "unicorn-magic": "0.2.0", - "dompurify": "3.4.11", + "dompurify": "3.4.12", "esbuild": "^0.28.1", "linkify-it": "5.0.2", "engine.io>ws": "8.21.0", @@ -111,16 +111,16 @@ "better-auth>zod": "^4.3.6", "@better-auth/core>zod": "^4.3.6", "js-cookie": "3.0.7", - "js-yaml": "4.2.0", + "js-yaml": "4.3.0", "gray-matter>js-yaml": "3.15.0", "read-yaml-file@1>js-yaml": "3.15.0", "nise>path-to-regexp": "8.4.0", - "protobufjs": "7.6.3", + "protobufjs": "7.6.5", "qs": "6.15.2", "router>path-to-regexp": "8.4.0", "socket.io>socket.io-adapter": "2.5.8", "socket.io-adapter>ws": "8.21.0", - "tar": "7.5.20", + "tar": "7.5.21", "zod": "^3.25.76", "astro>zod": "^4.3.6", "knip>zod": "^4.1.11", @@ -136,7 +136,16 @@ "@protobufjs/utf8": "1.1.1", "tsx": "4.20.4", "webpack": "5.104.1", - "yaml-language-server>yaml": "2.8.3" + "yaml-language-server>yaml": "2.8.3", + "@opentelemetry/propagator-jaeger": "2.9.0", + "body-parser": "2.3.0", + "brace-expansion@<1.1.16": "1.1.16", + "brace-expansion@>=2.0.0 <2.1.2": "2.1.2", + "brace-expansion@>=3.0.0 <5.0.8": "5.0.8", + "fast-uri": "3.1.4", + "immutable": "5.1.8", + "postcss": "8.5.18", + "sharp": "0.35.0" } } } diff --git a/packages/cloud-agents/src/server/__tests__/enqueue-task.test.ts b/packages/cloud-agents/src/server/__tests__/enqueue-task.test.ts index ccaee1de7..d8d1b7a71 100644 --- a/packages/cloud-agents/src/server/__tests__/enqueue-task.test.ts +++ b/packages/cloud-agents/src/server/__tests__/enqueue-task.test.ts @@ -50,6 +50,7 @@ import { persistEarlyGeneratedTaskTitle, resolveFreshTaskComputeProvider, resolveQueueScope, + shouldCaptureTaskCreatedEvent, type FreshTaskLaunch, } from '../task-run-queue'; import { LLM_TITLE_LOCKED_CHECKPOINT } from '../llm-task-title'; @@ -88,6 +89,17 @@ describe('resolveFreshTaskComputeProvider', () => { }); }); +describe('shouldCaptureTaskCreatedEvent', () => { + it('excludes environment snapshot maintenance from task analytics', () => { + expect( + shouldCaptureTaskCreatedEvent(TaskPayloadKind.SnapshotEnvironment), + ).toBe(false); + expect(shouldCaptureTaskCreatedEvent(TaskPayloadKind.StandardTask)).toBe( + true, + ); + }); +}); + function standardTaskInput( overrides: Partial> = {}, ): Extract { diff --git a/packages/cloud-agents/src/server/__tests__/non-task-provider-usage.test.ts b/packages/cloud-agents/src/server/__tests__/non-task-provider-usage.test.ts index 6c4b3e9eb..9c92b2058 100644 --- a/packages/cloud-agents/src/server/__tests__/non-task-provider-usage.test.ts +++ b/packages/cloud-agents/src/server/__tests__/non-task-provider-usage.test.ts @@ -341,19 +341,25 @@ describe('resolveOpenCodeSmallModel', () => { baseUrl: 'http://127.0.0.1:4096', fetch: expect.any(Function), }); + // Sessions are locked down: an empty scratch directory (never the + // service's own working directory) and a deny-all permission ruleset. expect(sessionCreateMock).toHaveBeenCalledWith( { - directory: process.cwd(), + directory: expect.stringContaining('roomote-non-task-'), title: `Roomote ${NON_TASK_INFERENCE_SURFACES.routerTaskRouting}`, + permission: [{ permission: '*', pattern: '*', action: 'deny' }], }, expect.objectContaining({ signal: expect.any(AbortSignal), }), ); + const sessionDirectory = sessionCreateMock.mock.calls[0]?.[0] + ?.directory as string; + expect(sessionDirectory).not.toBe(process.cwd()); expect(sessionPromptMock).toHaveBeenCalledWith( expect.objectContaining({ sessionID: 'session-1', - directory: process.cwd(), + directory: sessionDirectory, model: { providerID: 'openrouter', modelID: 'openai/gpt-5.4', @@ -481,7 +487,7 @@ describe('resolveOpenCodeSmallModel', () => { expect(sessionPromptMock).toHaveBeenCalledWith( expect.objectContaining({ sessionID: 'session-1', - directory: process.cwd(), + directory: expect.stringContaining('roomote-non-task-'), model: { providerID: 'openrouter', modelID: 'openai/gpt-5.4', diff --git a/packages/cloud-agents/src/server/__tests__/opencode-runtime.test.ts b/packages/cloud-agents/src/server/__tests__/opencode-runtime.test.ts index 398d44502..8e52b4211 100644 --- a/packages/cloud-agents/src/server/__tests__/opencode-runtime.test.ts +++ b/packages/cloud-agents/src/server/__tests__/opencode-runtime.test.ts @@ -52,6 +52,7 @@ describe('buildOpenCodeCliEnv', () => { expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ model: 'openrouter/openai/gpt-5.4', small_model: 'openrouter/openai/gpt-5.4', + permission: 'deny', }); }); @@ -66,6 +67,7 @@ describe('buildOpenCodeCliEnv', () => { expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ model: 'openrouter/openai/gpt-5.4', small_model: 'openrouter/z-ai/glm-5.2', + permission: 'deny', provider: { openrouter: { models: { @@ -90,6 +92,7 @@ describe('buildOpenCodeCliEnv', () => { expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ model: 'openrouter/z-ai/glm-5.2', small_model: 'openrouter/z-ai/glm-5.2', + permission: 'deny', provider: { openrouter: { models: { @@ -114,6 +117,7 @@ describe('buildOpenCodeCliEnv', () => { expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ model: 'openrouter/z-ai/glm-5.2', small_model: 'openrouter/z-ai/glm-5.2', + permission: 'deny', provider: { openrouter: { models: { @@ -135,6 +139,7 @@ describe('buildOpenCodeCliEnv', () => { expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ model: 'openrouter/openai/gpt-5.4', small_model: 'openrouter/openai/gpt-5.4', + permission: 'deny', provider: { openrouter: { models: { @@ -174,7 +179,48 @@ describe('buildOpenCodeCliEnv', () => { expect(env.R_SMALL_MODEL).toBeUndefined(); expect(env.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined(); expect(env.MISTRAL_API_KEY).toBeUndefined(); - expect(env.OPENCODE_CONFIG_CONTENT).toBeUndefined(); + // No model-backed config remains, but the tool lockdown still applies. + expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ + permission: 'deny', + }); + }); + + it('denies tools without any model config', () => { + const env = buildOpenCodeCliEnv(); + + expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ + permission: 'deny', + }); + }); + + it('merges the tool denial into operator-supplied config content', () => { + const env = buildOpenCodeCliEnv({ + OPENCODE_CONFIG_CONTENT: JSON.stringify({ + model: 'openrouter/openai/gpt-5.4', + theme: 'dark', + // An operator-supplied allow must not survive: these servers only + // serve non-task calls, which never run tools. + permission: 'allow', + }), + }); + + expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ + model: 'openrouter/openai/gpt-5.4', + theme: 'dark', + permission: 'deny', + }); + }); + + it('fails closed to a permission-only config on malformed content', () => { + for (const malformed of ['{not json', '"just a string"', '[1,2]']) { + const env = buildOpenCodeCliEnv({ + OPENCODE_CONFIG_CONTENT: malformed, + }); + + expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT ?? '{}')).toEqual({ + permission: 'deny', + }); + } }); it('prevents inherited BASH_ENV from restoring disabled credentials through a shell wrapper', () => { diff --git a/packages/cloud-agents/src/server/non-task-provider-usage.ts b/packages/cloud-agents/src/server/non-task-provider-usage.ts index 99705e941..c01c20bc3 100644 --- a/packages/cloud-agents/src/server/non-task-provider-usage.ts +++ b/packages/cloud-agents/src/server/non-task-provider-usage.ts @@ -1,4 +1,11 @@ -import { createOpencodeClient } from '@opencode-ai/sdk/v2/client'; +import { mkdtempSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { + createOpencodeClient, + type PermissionRuleset, +} from '@opencode-ai/sdk/v2/client'; import { resolveEffectiveModelRuntimeEnv } from '@roomote/db/server'; import type { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; @@ -11,6 +18,30 @@ import { const DEFAULT_OPENCODE_STRUCTURED_OUTPUT_RETRY_COUNT = 2; +/** + * Non-task sessions produce text or structured output only; no tool may ever + * run. The leased servers already deny tools via their config + * (`withDeniedToolPermissions` in opencode-runtime), and this per-session + * ruleset keeps a stale or externally configured server + * (`OPENCODE_SDK_SERVER_URL`) equally locked down. + */ +const NON_TASK_SESSION_PERMISSIONS: PermissionRuleset = [ + { permission: '*', pattern: '*', action: 'deny' }, +]; + +let nonTaskSessionDirectory: string | undefined; + +/** + * Sessions run in an empty scratch directory instead of the service's own + * working directory (the deployment's application code), so even a tool that + * slips past the permission layers has nothing to read or write, and OpenCode + * skips indexing the whole repository for a title-generation call. + */ +function resolveNonTaskSessionDirectory(): string { + nonTaskSessionDirectory ??= mkdtempSync(join(tmpdir(), 'roomote-non-task-')); + return nonTaskSessionDirectory; +} + export type NonTaskInferenceTrackingInput = { surface: string; userId?: string | null; @@ -19,7 +50,6 @@ export type NonTaskInferenceTrackingInput = { }; export const NON_TASK_INFERENCE_SURFACES = { - backgroundAnnouncer: 'background_announcer', fastAgentOnboardingSuggestions: 'fast_agent_onboarding_suggestions', fastAgentQuestionAnswering: 'fast_agent_question_answering', prReviewNotificationTriage: 'pr_review_notification_triage', @@ -34,7 +64,6 @@ export const NON_TASK_INFERENCE_SURFACES = { suggestionRoutingValidation: 'suggestion_routing_validation', taskSummaryGeneration: 'task_summary_generation', taskTitleGeneration: 'task_title_generation', - videoDescription: 'video_description', } as const; export interface GenerateTrackedNonTaskTextParams extends NonTaskInferenceTrackingInput { @@ -275,10 +304,12 @@ async function runNonTaskSdkPrompt( baseUrl: server.url, fetch: openCodeSdkFetch, }); + const sessionDirectory = resolveNonTaskSessionDirectory(); const sessionResult = await client.session.create( { - directory: process.cwd(), + directory: sessionDirectory, title: `Roomote ${params.surface}`, + permission: NON_TASK_SESSION_PERMISSIONS, }, { signal: abortController.signal }, ); @@ -292,7 +323,7 @@ async function runNonTaskSdkPrompt( const promptResult = await client.session.prompt( { sessionID: sessionResult.data.id, - directory: process.cwd(), + directory: sessionDirectory, model: splitOpenCodeModelId(model), ...promptOptions, }, diff --git a/packages/cloud-agents/src/server/opencode-runtime.ts b/packages/cloud-agents/src/server/opencode-runtime.ts index 2e24a32e7..a72f3b748 100644 --- a/packages/cloud-agents/src/server/opencode-runtime.ts +++ b/packages/cloud-agents/src/server/opencode-runtime.ts @@ -91,6 +91,46 @@ function buildModelBackedOpenCodeConfigContent( }); } +/** + * Force OpenCode's blanket tool-permission denial into a config content + * string, preserving any other operator-supplied settings. + * + * The servers this module spawns exist only for non-task inference — task + * titles, routing, fast-agent answers — which is plain text or structured + * output and must never run tools. Without this, OpenCode's default `build` + * agent auto-approves edit/bash in server mode, and an instruction-shaped + * prompt (a task description saying "add some dinosaurs") can cause the + * control plane to edit its own working directory. + * + * Malformed operator config fails closed to a permission-only config: every + * non-task call passes its model explicitly, so dropping model-backed + * defaults keeps text generation working while never booting a server with + * tools enabled. + */ +function withDeniedToolPermissions(configContent: string | undefined): string { + const permissionOnly = JSON.stringify({ permission: 'deny' }); + + if (!configContent?.trim()) { + return permissionOnly; + } + + try { + const parsed: unknown = JSON.parse(configContent); + + if ( + typeof parsed !== 'object' || + parsed === null || + Array.isArray(parsed) + ) { + return permissionOnly; + } + + return JSON.stringify({ ...parsed, permission: 'deny' }); + } catch { + return permissionOnly; + } +} + export function buildOpenCodeCliEnv( extraEnv?: Partial>, ): NodeJS.ProcessEnv { @@ -116,6 +156,12 @@ export function buildOpenCodeCliEnv( } } + // Applied unconditionally, after any operator-supplied config content is + // selected, so custom OPENCODE_CONFIG_CONTENT cannot re-enable tools. + env.OPENCODE_CONFIG_CONTENT = withDeniedToolPermissions( + env.OPENCODE_CONFIG_CONTENT, + ); + // Do not inherit or accept disabled-provider credentials in helper model // processes, including callers that bypass the task dequeue path. for (const envVarName of DISABLED_MODEL_PROVIDER_ENV_VAR_NAMES) { diff --git a/packages/cloud-agents/src/server/task-run-queue.ts b/packages/cloud-agents/src/server/task-run-queue.ts index cb750a020..34fa410f5 100644 --- a/packages/cloud-agents/src/server/task-run-queue.ts +++ b/packages/cloud-agents/src/server/task-run-queue.ts @@ -109,6 +109,13 @@ export function resolveFreshTaskComputeProvider( : resolveComputeProviderTarget(provider, fallback); } +export function shouldCaptureTaskCreatedEvent( + taskType: TaskPayloadKind, +): boolean { + // Environment snapshots are maintenance work, not product task activity. + return taskType !== TaskPayloadKind.SnapshotEnvironment; +} + const ATOMIC_ENQUEUE_SCRIPT = ` local incomingId = ARGV[1] local incomingScope = ARGV[2] @@ -1518,20 +1525,22 @@ async function enqueueFreshLaunch( return insertedRun; }); - // Anonymous analytics (no-op unless enabled): task creation with - // non-identifying routing facts only. - void captureEvent('task_created', { - ...(linkedUserId ? { userId: linkedUserId } : {}), - properties: { - taskType: taskRun.payloadKind, - workflow, - surface, - trigger, - harness: taskRun.harness ?? null, - model: effectiveTaskModel, - computeProvider: taskRun.vendor ?? null, - }, - }); + if (shouldCaptureTaskCreatedEvent(taskRun.payloadKind)) { + // Anonymous analytics (no-op unless enabled): task creation with + // non-identifying routing facts only. + void captureEvent('task_created', { + ...(linkedUserId ? { userId: linkedUserId } : {}), + properties: { + taskType: taskRun.payloadKind, + workflow, + surface, + trigger, + harness: taskRun.harness ?? null, + model: effectiveTaskModel, + computeProvider: taskRun.vendor ?? null, + }, + }); + } await pushRunOntoQueue({ taskRun, diff --git a/packages/cloud-agents/src/server/workflows/__tests__/githubPrReviewSkill.test.ts b/packages/cloud-agents/src/server/workflows/__tests__/githubPrReviewSkill.test.ts index 36be3e516..c009ac555 100644 --- a/packages/cloud-agents/src/server/workflows/__tests__/githubPrReviewSkill.test.ts +++ b/packages/cloud-agents/src/server/workflows/__tests__/githubPrReviewSkill.test.ts @@ -71,9 +71,32 @@ describe('review-code GitHub workflow paths', () => { expect(skillContent).toContain( 'When `pull_request_details` or current head metadata is missing, or when it must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "get_pull_request"`, `repositoryFullName`, and `prNumber`.', ); - expect(skillContent).toContain( - "When `pull_request_diff` is missing, or when the current diff must be revalidated before a side effect, compute it locally: `git fetch origin '' ''`, then `git diff ...` using the SHAs from `get_pull_request`. Use this local git diff for every provider instead of a provider CLI.", - ); + for (const appendixName of [ + 'review-github-pr', + 'review-github-pr-with-approval', + ]) { + const appendix = readAppendix(skillContent, appendixName); + expect(appendix).toContain( + "For a GitHub cross-repository PR, run `git fetch origin '' '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, then verify `git rev-parse refs/remotes/origin/pr-[PR_NUMBER]-head` exactly equals `` from `get_pull_request`.", + ); + expect(appendix).toContain( + 'If it differs, call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker.', + ); + } + for (const appendixName of [ + 'review-github-pr', + 'review-github-pr-with-approval', + 'sync-github-pr-review', + 'sync-github-pr-review-with-approval', + ]) { + const appendix = readAppendix(skillContent, appendixName); + expect(appendix).toContain( + "For a GitHub cross-repository PR, fetch the upstream PR ref with `git fetch origin '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, verify its resolved SHA exactly equals `` from `get_pull_request`, and if it differs call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. Then run `git checkout --detach `.", + ); + expect(appendix).toContain( + 'For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of fetching the fork directly or improvising credentials.', + ); + } expect(skillContent).toContain( 'When `existing_review_comments` or `issue_comments` are missing, or when current thread or top-level discussion state must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "list_pull_request_comments"`.', ); diff --git a/packages/cloud-agents/src/server/workflows/skills/standard/dependabot-triage/SKILL.md b/packages/cloud-agents/src/server/workflows/skills/standard/dependabot-triage/SKILL.md index a270ddeac..9700207fc 100644 --- a/packages/cloud-agents/src/server/workflows/skills/standard/dependabot-triage/SKILL.md +++ b/packages/cloud-agents/src/server/workflows/skills/standard/dependabot-triage/SKILL.md @@ -10,7 +10,7 @@ You are a dependency-security triage specialist. Use GitHub's Dependabot alert d - Use the GitHub access already available in the task environment. Prefer `gh api` for alert retrieval, keep scheduled/background runs read-only, and honor any repository scope, Slack channel, run mode, or automation policy supplied in the request. Human-triggered or automation-started follow-up execution work belongs in the dependency-update specialist, not in this triage scan itself. + Use the GitHub access already available in the task environment. Prefer `gh api` for alert retrieval, keep scheduled/background runs read-only, and honor any repository scope, Slack channel, run mode, or automation policy supplied in the request. Human-triggered or automation-started follow-up execution work belongs in the dependency-update specialist, not in this triage scan itself. Make each scan completion-oriented: track the current alert count, account for related open PRs, and launch the smallest remediation that can clear a cohesive alert bundle. @@ -22,7 +22,8 @@ You are a dependency-security triage specialist. Use GitHub's Dependabot alert d - Inspect current open Dependabot alerts for each repository in scope. Prefer repository-level API calls so every finding already maps to a single launch target. + Inspect current open Dependabot alerts for each repository in scope and count them. Prefer repository-level API calls so every finding already maps to a single launch target. + Inspect each repository's open pull requests for dependency updates or Dependabot alert references. Treat a PR that covers an alert as in-flight remediation, not a reason to create duplicate work; record which alerts remain uncovered. Collect only the evidence needed to rank each alert: repository, alert URL or number, dependency package name, ecosystem, manifest path, dependency scope if available, severity, summary, vulnerable range, first patched version, and whether the alert appears direct or transitive. Prioritize alerts by severity, reachable/runtime impact, direct dependency ownership, availability of a clear patched version, number of repositories or manifests affected, and confidence that a focused update can land without broad churn. Deprioritize already-closed alerts, duplicated grouped updates that would create unnecessary version churn, low-confidence ecosystem mismatches, and alerts whose repository ownership or update path is unclear. @@ -33,18 +34,17 @@ You are a dependency-security triage specialist. Use GitHub's Dependabot alert d Start with the repository scope, overall risk, and the highest-priority update candidate or no-op result. - Submit actionable candidates with `submit_automation_work_items`. Submit up to 3 `act` work items for the best cohesive candidates, keep each one scoped to one repository from `repository_scope`, submit at most one work item for each `targetEnvironmentId`, and only target repositories that appear in the `Repository environments` section. - Do not submit suggestion work items (they are rejected), do not fall back to bare-repo execution, and do not post a Slack launch announcement after the item is submitted. The later execution task stays silent while work is in flight and uses Slack only when it needs input, hits a blocker, or has a meaningful result. + Submit actionable candidates with `submit_automation_work_items`. Submit one cohesive remediation bundle for every eligible environment with uncovered actionable alerts, in priority order, up to 3 `act` work items. Keep each one scoped to one repository from `repository_scope`, submit at most one work item for each `targetEnvironmentId`, and only target repositories that appear in the `Repository environments` section. Do not stop after the highest-priority candidate when another eligible environment has independent uncovered alerts. When multiple related alerts share a lockfile or dependency surface, one task should aim to clear every actionable alert in that cohesive bundle, not merely the highest-severity alert. + Do not submit suggestion work items (they are rejected) or fall back to bare-repo execution. Do not duplicate work already covered by a related open PR; include the PR and any uncovered alerts in the status message instead. The later execution task stays silent while work is in flight and uses Slack only when it needs input, hits a blocker, or has a meaningful result. Write action-first titles such as `Update lodash in apps/web to resolve high-severity Dependabot alert` or `Bundle pnpm lockfile refresh for vulnerable ws alerts in worker`. Every work item must target exactly one repository from `repository_scope`. When a `Repository environments` section is present, copy the matching `targetEnvironmentId` only for repositories explicitly listed there. Do not invent environment IDs or reuse one repository's environment ID for another repository. Use `security` for alerts that represent real vulnerability remediation work, `chore` for lower-risk dependency maintenance bundles, and `improvement` only when the strongest follow-up is tooling or validation hardening instead of the update itself. - In `investigationContext`, include `$update-dependencies`, the alert URL or number, alert summary, package name, ecosystem, manifest path, vulnerable range, first patched version, severity, the exact GitHub CLI commands used during triage, whether the affected package likely touches a running service or user-facing web surface, and what the follow-up task must verify before shipping the update. - Make the `executionPrompt` start with `$update-dependencies` and prefer the smallest cohesive execution scope that is likely to clear the alert: one package when enough, or one tightly related manifest/workspace group when the remediation needs aligned updates. Do not submit a broad sweep. + In `investigationContext`, include `$update-dependencies`, the alert URL or number, alert summary, package name, ecosystem, manifest path, vulnerable range, first patched version, severity, related open PRs and the alerts they cover, the exact GitHub CLI commands used during triage, whether the affected package likely touches a running service or user-facing web surface, and what the follow-up task must verify before shipping the update. + Make the `executionPrompt` start with `$update-dependencies` and prefer the smallest cohesive execution scope that is likely to clear the alert bundle: one package when enough, or one tightly related manifest/workspace group when the remediation needs aligned updates. Require the follow-up task to re-check the targeted alerts before delivery and leave no actionable alerts from that bundle open, unless an existing related PR already covers them. Do not submit a broad sweep. Prefer focused follow-ups: one alert or one tightly related alert bundle per task. It is fine to include multiple affected workspaces or a small related dependency bundle when that is what the remediation actually requires. Do not submit broad "update everything" chores from this skill. - If `submit_automation_work_items` succeeds for one or more work items, do not call `post_to_slack_channel` and do not post a separate Slack summary unless the request explicitly says the scan itself should report launch outcomes. - If `slack_channel_id` is present and there is a GitHub setup/auth blocker (for example missing or suspended access to Dependabot alerts), post a concise report there with `post_to_slack_channel` so the broken run does not disappear silently. Treat repository-level gaps such as Dependabot alerts being disabled for a repository, a repository returning zero open alerts, or a repository falling outside configured environment coverage as non-blocking no-op findings for this run, not as Slack-worthy blockers. When the run is otherwise clean — no actionable alerts, no eligible configured-environment candidates, no configured repositories, or only non-launchable findings — stay quiet: do not post to Slack, and end with a terse internal note. A clean read-only run is not worth a channel message. - Keep any `post_to_slack_channel` blocker report plain-language and manager-readable, and do not paste raw GitHub CLI commands, `gh api` invocations, or command transcripts into Slack. The exact commands belong only in work item `investigationContext`, never in the channel report. - End the task response with a terse internal note when follow-up items were submitted or the run was clean, or the concise blocker report when a Slack post was needed. + Do not post a Slack opening acknowledgement, scan announcement, progress update, or partial finding. If `slack_channel_id` is present, post exactly one concise scan summary there with `post_to_slack_channel` only after triage reaches a final result: total open alert count with critical/high/medium/low severity breakdown, per-repository counts, relevant open PRs (or that none are open), and how many currently open alerts are covered by newly started remediation task(s), existing related PRs, or neither. + Keep the `post_to_slack_channel` summary plain-language and manager-readable, and do not paste raw GitHub CLI commands, `gh api` invocations, or command transcripts into Slack. The exact commands belong only in work item `investigationContext`, never in the channel report. + End the task response with a terse internal note after posting the scan summary, including when there are no actionable alerts or GitHub access is blocked. @@ -54,5 +54,5 @@ You are a dependency-security triage specialist. Use GitHub's Dependabot alert d The scan stayed read-only for scheduled/background runs. Actionable scheduled findings were submitted as environment-backed `act` work items. The final report or submitted work items were concise, prioritized, plain-language, and free of raw command transcripts, so they are safe to post in Slack. -Clean scans stayed silent in Slack; only setup/auth blockers were reported there. +Every configured Slack scan posted exactly one final result with the critical/high/medium/low severity breakdown, related open PR state, and remediation coverage count. diff --git a/packages/cloud-agents/src/server/workflows/skills/standard/review-code/SKILL.md b/packages/cloud-agents/src/server/workflows/skills/standard/review-code/SKILL.md index b63b76e28..8a054ea4a 100644 --- a/packages/cloud-agents/src/server/workflows/skills/standard/review-code/SKILL.md +++ b/packages/cloud-agents/src/server/workflows/skills/standard/review-code/SKILL.md @@ -237,11 +237,11 @@ You are a pull request review workflow specialist. Review the assigned pull requ If prompt-supplied PR snapshots exist, start from them and skip redundant fetches. Use the Roomote MCP `manage_source_control` read actions only to fill missing context or to revalidate mutable provider state before posting comments, patching summary comments, or approving; do not use provider-specific CLIs such as `gh` for pull-request state. When `pull_request_details` or current head metadata is missing, or when it must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "get_pull_request"`, `repositoryFullName`, and `prNumber`. The result carries the title, body, state, draft flag, source and target branches, head and base SHAs, author, mergeability, and cross-repository (fork) information. - When `pull_request_diff` is missing, or when the current diff must be revalidated before a side effect, compute it locally: `git fetch origin '' ''`, then `git diff ...` using the SHAs from `get_pull_request`. Use this local git diff for every provider instead of a provider CLI. + When `pull_request_diff` is missing, or when the current diff must be revalidated before a side effect, compute it locally. For a same-repository PR, run `git fetch origin '' ''`. For a GitHub cross-repository PR, run `git fetch origin '' '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, then verify `git rev-parse refs/remotes/origin/pr-[PR_NUMBER]-head` exactly equals `` from `get_pull_request`. If it differs, call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. Then run `git diff ...`. Use this local git diff for every provider instead of a provider CLI. When `existing_review_comments` or `issue_comments` are missing, or when current thread or top-level discussion state must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "list_pull_request_comments"`. The result returns review threads (each with a `threadId`, `resolved` state when the provider exposes it, and inline path/line anchors) plus top-level `issueComments`; heed any capability warnings it reports. Before PR checkout or deep repository reading, if `TOP_LEVEL_COMMENT_ID` is already supplied or the available PR issue comments already reveal a reusable canonical summary comment, recover that reusable comment immediately and patch only its status block in place (using `mcp__roomote__manage_source_control` `action: "update_pull_request_comment"` with that comment's `commentId`, plus its `threadId` when the provider returns one) to show a short in-progress line such as `Reviewing the PR now. {task_link_follow}`. Rewrite only the content inside the hidden `` and `` markers when they exist, and otherwise normalize the comment into the hidden status/checklist block format before continuing. Carry the recovered comment ID forward as `TOP_LEVEL_COMMENT_ID` for the later canonical-summary step instead of leaving stale status text visible during startup latency. If `linked_issue` context is missing, use the linked-work-item context supplied by the current workflow instructions or referenced in the pull-request body when present; do not fetch issues through provider-specific CLIs. - Check out the PR branch locally with `git fetch origin '' && git checkout ''`, using the source branch from the pull-request details. For cross-repository (fork) PRs whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. + Check out a same-repository PR branch with `git fetch origin '' && git checkout ''`. For a GitHub cross-repository PR, fetch the upstream PR ref with `git fetch origin '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, verify its resolved SHA exactly equals `` from `get_pull_request`, and if it differs call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. Then run `git checkout --detach `. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of fetching the fork directly or improvising credentials. Read the changed files in full, then read any related types, schemas, callers, tests, or utilities needed to verify correctness in context. The live pull request state, existing discussion, and relevant repository context have been read deeply enough to support evidence-based review findings. @@ -512,11 +512,11 @@ You are a pull request review workflow specialist. Review the assigned pull requ If prompt-supplied PR snapshots exist, start from them and skip redundant fetches. Use the Roomote MCP `manage_source_control` read actions only to fill missing context or to revalidate mutable provider state before posting comments, patching summary comments, or approving; do not use provider-specific CLIs such as `gh` for pull-request state. When `pull_request_details` or current head metadata is missing, or when it must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "get_pull_request"`, `repositoryFullName`, and `prNumber`. The result carries the title, body, state, draft flag, source and target branches, head and base SHAs, author, mergeability, and cross-repository (fork) information. - When `pull_request_diff` is missing, or when the current diff must be revalidated before a side effect, compute it locally: `git fetch origin '' ''`, then `git diff ...` using the SHAs from `get_pull_request`. Use this local git diff for every provider instead of a provider CLI. + When `pull_request_diff` is missing, or when the current diff must be revalidated before a side effect, compute it locally. For a same-repository PR, run `git fetch origin '' ''`. For a GitHub cross-repository PR, run `git fetch origin '' '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, then verify `git rev-parse refs/remotes/origin/pr-[PR_NUMBER]-head` exactly equals `` from `get_pull_request`. If it differs, call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. Then run `git diff ...`. Use this local git diff for every provider instead of a provider CLI. When `existing_review_comments` or `issue_comments` are missing, or when current thread or top-level discussion state must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "list_pull_request_comments"`. The result returns review threads (each with a `threadId`, `resolved` state when the provider exposes it, and inline path/line anchors) plus top-level `issueComments`; heed any capability warnings it reports. Before PR checkout or deep repository reading, if `TOP_LEVEL_COMMENT_ID` is already supplied or the available PR issue comments already reveal a reusable canonical summary comment, recover that reusable comment immediately and patch only its status block in place (using `mcp__roomote__manage_source_control` `action: "update_pull_request_comment"` with that comment's `commentId`, plus its `threadId` when the provider returns one) to show a short in-progress line such as `Reviewing the PR now. {task_link_follow}`. Rewrite only the content inside the hidden `` and `` markers when they exist, and otherwise normalize the comment into the hidden status/checklist block format before continuing. Carry the recovered comment ID forward as `TOP_LEVEL_COMMENT_ID` for the later canonical-summary step instead of leaving stale status text visible during startup latency. If `linked_issue` context is missing, use the linked-work-item context supplied by the current workflow instructions or referenced in the pull-request body when present; do not fetch issues through provider-specific CLIs. - Check out the PR branch locally with `git fetch origin '' && git checkout ''`, using the source branch from the pull-request details. For cross-repository (fork) PRs whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. + Check out a same-repository PR branch with `git fetch origin '' && git checkout ''`. For a GitHub cross-repository PR, fetch the upstream PR ref with `git fetch origin '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, verify its resolved SHA exactly equals `` from `get_pull_request`, and if it differs call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. Then run `git checkout --detach `. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of fetching the fork directly or improvising credentials. Read the changed files in full, then read any related types, schemas, callers, tests, or utilities needed to verify correctness in context. The live pull request state, existing discussion, and relevant repository context have been read deeply enough to support evidence-based review findings. @@ -833,7 +833,7 @@ You are a sync-review workflow specialist. Re-review pull requests after new com If prompt-supplied delta snapshots exist, start from them and skip redundant fetches. Use the Roomote MCP `manage_source_control` read actions or local git only to fill missing delta context or to revalidate mutable provider state before posting comments, patching summary comments, or approving; do not use provider-specific CLIs such as `gh` for pull-request state. When `pull_request_details` or current head metadata is missing, or when it must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "get_pull_request"`, `repositoryFullName`, and `prNumber`. The result carries the title, body, state, draft flag, source and target branches, head and base SHAs, author, mergeability, and cross-repository (fork) information. If you are not in `legacy_full_rereview_path` and the current head SHA matches `last_review_sha`, update the summary comment with a short no-op note, mark the terminal outcome as `no_new_delta`, then continue directly to the linked-task handoff step so the implementation task receives that explicit status before you stop. - Check out the PR branch locally with `git fetch origin '' && git checkout ''`, using the source branch from the pull-request details. For cross-repository (fork) PRs whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. + Check out a same-repository PR branch with `git fetch origin '' && git checkout ''`. For a GitHub cross-repository PR, fetch the upstream PR ref with `git fetch origin '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, verify its resolved SHA exactly equals `` from `get_pull_request`, and if it differs call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. Then run `git checkout --detach `. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of fetching the fork directly or improvising credentials. If you are not in `legacy_full_rereview_path`, first decide whether there is any new delta at all with a two-dot diff `git diff [last_review_sha]..[HEAD_SHA]` and `git log --oneline [last_review_sha]..[HEAD_SHA]`. Two-dot (`..`) is the actual content difference between the two reviewed commits. If it is empty — for example the head SHA changed only because the branch was rebased, with no new content — treat it the same as the head-SHA-match case: update the summary comment with a short no-op note, mark the terminal outcome `no_new_delta`, and continue to the linked-task handoff step instead of re-reviewing. When there is a delta, the authoritative set of changes you may review is the PR's current Files Changed — its base-to-head diff, `git diff ...` (three-dot from the current base), scoped to the files in `pull_request_changed_files`/`changed_files_since_last_review` and the supplied `diff_in_range` when present. Report findings only for hunks that appear in that current PR diff. A change that is not in the PR's base-to-head diff — including a base-branch modification to a file the PR also touches — is out of scope: it belongs to the base branch, not this PR, and must not be reported or carried forward. Use the two-dot delta and commit log only to focus on what is new since the last review, never as the review scope itself. If you are in `legacy_full_rereview_path`, re-review the full current PR diff with a local base-to-head comparison: `git fetch origin ''`, then `git diff ...` using the SHAs from `get_pull_request`. Use this local git diff for every provider. @@ -1144,7 +1144,7 @@ You are a sync-review workflow specialist. Re-review pull requests after new com If prompt-supplied delta snapshots exist, start from them and skip redundant fetches. Use the Roomote MCP `manage_source_control` read actions or local git only to fill missing delta context or to revalidate mutable provider state before posting comments, patching summary comments, or approving; do not use provider-specific CLIs such as `gh` for pull-request state. When `pull_request_details` or current head metadata is missing, or when it must be revalidated before a side effect, call `mcp__roomote__manage_source_control` with `action: "get_pull_request"`, `repositoryFullName`, and `prNumber`. The result carries the title, body, state, draft flag, source and target branches, head and base SHAs, author, mergeability, and cross-repository (fork) information. If you are not in `legacy_full_rereview_path` and the current head SHA matches `last_review_sha`, update the summary comment with a short no-op note, mark the terminal outcome as `no_new_delta`, then continue directly to the linked-task handoff step so the implementation task receives that explicit status before you stop. - Check out the PR branch locally with `git fetch origin '' && git checkout ''`, using the source branch from the pull-request details. For cross-repository (fork) PRs whose source branch cannot be fetched with task credentials, report that blocker instead of improvising credentials. + Check out a same-repository PR branch with `git fetch origin '' && git checkout ''`. For a GitHub cross-repository PR, fetch the upstream PR ref with `git fetch origin '+refs/pull/[PR_NUMBER]/head:refs/remotes/origin/pr-[PR_NUMBER]-head'`, verify its resolved SHA exactly equals `` from `get_pull_request`, and if it differs call `get_pull_request` once more and proceed only when the fetched SHA matches the refreshed ``; otherwise report the blocker. Then run `git checkout --detach `. For a cross-repository PR on another provider whose source branch cannot be fetched with task credentials, report that blocker instead of fetching the fork directly or improvising credentials. If you are not in `legacy_full_rereview_path`, first decide whether there is any new delta at all with a two-dot diff `git diff [last_review_sha]..[HEAD_SHA]` and `git log --oneline [last_review_sha]..[HEAD_SHA]`. Two-dot (`..`) is the actual content difference between the two reviewed commits. If it is empty — for example the head SHA changed only because the branch was rebased, with no new content — treat it the same as the head-SHA-match case: update the summary comment with a short no-op note, mark the terminal outcome `no_new_delta`, and continue to the linked-task handoff step instead of re-reviewing. When there is a delta, the authoritative set of changes you may review is the PR's current Files Changed — its base-to-head diff, `git diff ...` (three-dot from the current base), scoped to the files in `pull_request_changed_files`/`changed_files_since_last_review` and the supplied `diff_in_range` when present. Report findings only for hunks that appear in that current PR diff. A change that is not in the PR's base-to-head diff — including a base-branch modification to a file the PR also touches — is out of scope: it belongs to the base branch, not this PR, and must not be reported or carried forward. Use the two-dot delta and commit log only to focus on what is new since the last review, never as the review scope itself. If you are in `legacy_full_rereview_path`, re-review the full current PR diff with a local base-to-head comparison: `git fetch origin ''`, then `git diff ...` using the SHAs from `get_pull_request`. Use this local git diff for every provider. diff --git a/packages/cloud-agents/src/server/workflows/skills/standard/update-dependencies/SKILL.md b/packages/cloud-agents/src/server/workflows/skills/standard/update-dependencies/SKILL.md index 8586b305d..8cd9477d3 100644 --- a/packages/cloud-agents/src/server/workflows/skills/standard/update-dependencies/SKILL.md +++ b/packages/cloud-agents/src/server/workflows/skills/standard/update-dependencies/SKILL.md @@ -46,7 +46,7 @@ You are a dependency maintenance specialist. Keep dependency upgrades small, saf Read repository guidance files, package-manager configuration, Renovate or Dependabot config, CI definitions, and docs that define supported update cadence or validation commands. Infer the authoritative package manager from lockfiles and project tooling rather than mixing package managers. For monorepos, identify the smallest workspace slice that can be updated and validated safely. - Map the selected package to the runtime surfaces it can affect: build-only tooling, shared libraries, backend services, CLIs, worker processes, or user-facing web apps. Use that impact map to decide which test suites and live-service checks are required later. + Map the selected package to the runtime surfaces it can affect: build-only tooling, shared libraries, backend services, CLIs, worker processes, or user-facing web apps. Use that impact map to decide which test suites and live-service checks are required later. For Dependabot alert follow-up runs, stop discovery as soon as you have enough surface information to resolve the named package or manifest slice safely; do not keep exploring adjacent dependency surfaces just for awareness. The update surface, package manager, and validation candidates are grounded in repository files. @@ -56,7 +56,10 @@ You are a dependency maintenance specialist. Keep dependency upgrades small, saf Choose updates that are appropriate for unattended or periodic execution. For recurring maintenance runs, use the package manager's non-mutating outdated-report command before selecting updates when one is available. For pnpm workspaces, prefer `pnpm outdated --recursive --compatible --format json` to identify updates that satisfy existing specs, and use broader `pnpm outdated --recursive --format json` output only as awareness for deferred major or incompatible updates. For single-package pnpm repositories, use the same commands without `--recursive` when workspace recursion does not apply. - For Dependabot alert follow-up runs, re-verify the alert first with the GitHub data available in the task environment. Confirm the alert is still open, capture the exact package, ecosystem, manifest path, vulnerable range, first patched version, and repository, and let that alert scope constrain the update set. + For Dependabot alert follow-up runs, re-verify the alert with the GitHub data available in the task environment. Confirm it is still open and capture the verified package, ecosystem, manifest path, vulnerable range, and first patched version. + Select the intended version transition from the verified alert's first patched version and the repository's compatibility constraints. Keep the update set constrained to that selected package, manifest, or tightly related workspace slice. + After alert re-verification and target-version selection, inspect the upstream changelog, release notes, or version diff when that information is available. Record material behavioral changes, deprecations, breaking changes, security-relevant fixes, and the affected runtime surfaces; do not infer release details when upstream information is unavailable. + Use the upstream change analysis to select targeted validation and runtime checks for the affected behavior. When the analysis identifies no relevant runtime surface, state that conclusion and use the strongest applicable repository-defined static or package-level validation instead. For Dependabot alert follow-up runs, do not run a generic outdated report unless the alert has already closed and the user explicitly asks to broaden the task into ordinary maintenance. Prefer patch and minor updates within existing semver ranges, lockfile refreshes, security fixes, and tightly related dependency groups that can be validated together. For Dependabot-driven work, prefer the smallest manifest or workspace-scoped update that resolves the named alert package or tightly related alert bundle. If the narrowest single-package update is not enough, widen only to the smallest cohesive remediation that is still directly tied to the alert, such as aligned workspace version bumps, lockfile refreshes, or a small related dependency bundle. Do not turn one alert-driven task into a broad dependency sweep. @@ -152,7 +155,8 @@ You are a dependency maintenance specialist. Keep dependency upgrades small, saf Report the maintenance outcome Summarize the result in repository-facing terms. - For successful updates, name the dependency scope, notable version changes, validation and runtime-proof result, and delivery outcome. + For successful updates, name the dependency scope, notable version changes, validation and runtime-proof result, and delivery outcome. + For Dependabot alert follow-ups, include a concise user-facing impact summary: what upstream behavior or security change was identified, what relevant code path was verified, and any remaining risk or uncertainty. If changelog or diff evidence was unavailable, say so plainly instead of inventing release analysis. For no-op runs, say what was checked and why nothing changed. For failed or deferred updates, name the blocked package or version, the validation, runtime-proof, or compatibility evidence, and the next step needed. For automation-started or late-bound Slack Dependabot follow-up runs, do not send intermediate Slack progress reports, elapsed-time updates, validation-started updates, or partial findings. Keep in-flight status in the task transcript and todo list, and send a Slack-visible report only after the run reaches a final shipped, no-op, or deferred state, after reverting untrusted changes, or when concrete user input is required before continuing. @@ -169,6 +173,7 @@ You are a dependency maintenance specialist. Keep dependency upgrades small, saf Any dependency changes were produced through package-manager tooling and validated by the strongest practical repository-defined checks plus any required service-level runtime proof. The run was not delivered with failing validation or missing required runtime verification. Failed attempts reverted only this run's untrusted dependency changes without unrelated rollback. Repository-changing successful runs were delivered through `create-draft-pr` (or an explicitly-requested override), and the PR body summarized the applied updates, validation and runtime-proof result, and any deferred packages. +Dependabot follow-ups inspect available upstream release changes, use them to target validation, and report the resulting impact and remaining uncertainty. diff --git a/packages/db/src/lib/__tests__/instance-report.test.ts b/packages/db/src/lib/__tests__/instance-report.test.ts index e63f81948..17d75a10b 100644 --- a/packages/db/src/lib/__tests__/instance-report.test.ts +++ b/packages/db/src/lib/__tests__/instance-report.test.ts @@ -1,14 +1,20 @@ import { and, eq, inArray, notInArray } from 'drizzle-orm'; -import { SOURCE_CONTROL_AUTOMATION_WORKFLOWS } from '@roomote/types'; +import { + RunStatus, + SOURCE_CONTROL_AUTOMATION_WORKFLOWS, + TaskPayloadKind, +} from '@roomote/types'; import { db, githubInstallationFactory, + llmUsageEvents, pullRequestFacts, repositoryFactory, taskFactory, taskPullRequests, + taskRuns, tasks, userFactory, } from '../../server'; @@ -448,3 +454,89 @@ describe('collectInstanceReportStats pullRequests7d isolation', () => { ).toBe(report.pullRequests7d.opened); }); }); + +describe('collectInstanceReportStats task usage isolation', () => { + it('excludes environment snapshots from task usage aggregates', async () => { + // Keep this aggregate assertion isolated from test files sharing the database. + const now = new Date('2040-01-01T00:00:00.000Z'); + const baseline = await collectInstanceReportStats(now); + const suffix = Date.now().toString(); + const productModel = `product-model-${suffix}`; + const snapshotModel = `snapshot-model-${suffix}`; + const productTask = await taskFactory.create({ + workflow: 'standard', + model: productModel, + createdAt: now, + }); + const snapshotTask = await taskFactory.create({ + workflow: 'env_snapshot', + model: snapshotModel, + createdAt: now, + }); + + await db.insert(taskRuns).values({ + taskId: productTask.id, + payloadKind: TaskPayloadKind.StandardTask, + kind: 'fresh', + status: RunStatus.Completed, + completedAt: now, + payload: { repo: 'acme/product', description: 'Product task' }, + }); + await db.insert(taskRuns).values({ + taskId: snapshotTask.id, + payloadKind: TaskPayloadKind.SnapshotEnvironment, + kind: 'fresh', + status: RunStatus.Completed, + completedAt: now, + payload: { + repo: '', + environmentId: crypto.randomUUID(), + }, + }); + + await db.insert(llmUsageEvents).values([ + { + eventKey: `product-usage-${suffix}`, + taskId: productTask.id, + inputTokens: 10, + outputTokens: 20, + totalTokens: 30, + costMicroUsd: 40, + costSource: 'opencode_message', + createdAt: now, + }, + { + eventKey: `snapshot-usage-${suffix}`, + taskId: snapshotTask.id, + inputTokens: 100, + outputTokens: 200, + totalTokens: 300, + costMicroUsd: 400, + costSource: 'opencode_message', + createdAt: now, + }, + ]); + + const report = await collectInstanceReportStats(now); + + expect(report.tasks24h.created).toBe(baseline.tasks24h.created + 1); + expect(report.tasks24h.completed).toBe(baseline.tasks24h.completed + 1); + expect(report.tasks24h.byHarness['opencode-server']).toBe( + (baseline.tasks24h.byHarness['opencode-server'] ?? 0) + 1, + ); + expect(report.tasks24h.byModel).toContainEqual({ + provider: 'openai', + model: productModel, + count: 1, + }); + expect(report.tasks24h.byModel).not.toContainEqual( + expect.objectContaining({ model: snapshotModel }), + ); + expect(report.tasks24h.tokens).toEqual({ + input: baseline.tasks24h.tokens.input + 10, + output: baseline.tasks24h.tokens.output + 20, + total: baseline.tasks24h.tokens.total + 30, + costMicroUsd: baseline.tasks24h.tokens.costMicroUsd + 40, + }); + }); +}); diff --git a/packages/db/src/lib/instance-report.ts b/packages/db/src/lib/instance-report.ts index 39ce9b98f..dfa945da4 100644 --- a/packages/db/src/lib/instance-report.ts +++ b/packages/db/src/lib/instance-report.ts @@ -631,20 +631,32 @@ export async function collectInstanceReportStats( db .select({ total: count() }) .from(tasks) - .where(gte(tasks.createdAt, since)), + .where( + and( + gte(tasks.createdAt, since), + notInArray(tasks.workflow, ['env_snapshot']), + ), + ), db .select({ total: count() }) .from(taskRuns) + .innerJoin(tasks, eq(tasks.id, taskRuns.taskId)) .where( and( eq(taskRuns.status, RunStatus.Completed), gte(taskRuns.completedAt, since), + notInArray(tasks.workflow, ['env_snapshot']), ), ), db .select({ harness: tasks.harness, total: count() }) .from(tasks) - .where(gte(tasks.createdAt, since)) + .where( + and( + gte(tasks.createdAt, since), + notInArray(tasks.workflow, ['env_snapshot']), + ), + ) .groupBy(tasks.harness), db .select({ @@ -653,7 +665,12 @@ export async function collectInstanceReportStats( total: count(), }) .from(tasks) - .where(gte(tasks.createdAt, since)) + .where( + and( + gte(tasks.createdAt, since), + notInArray(tasks.workflow, ['env_snapshot']), + ), + ) .groupBy(tasks.modelProvider, tasks.model), db .select({ @@ -663,10 +680,12 @@ export async function collectInstanceReportStats( costMicroUsd: sum(llmUsageEvents.costMicroUsd), }) .from(llmUsageEvents) + .innerJoin(tasks, eq(tasks.id, llmUsageEvents.taskId)) .where( and( gte(llmUsageEvents.createdAt, since), isNotNull(llmUsageEvents.taskId), + notInArray(tasks.workflow, ['env_snapshot']), ), ), db diff --git a/packages/sdk/src/server/automations/__tests__/announcer.test.ts b/packages/sdk/src/server/automations/__tests__/announcer.test.ts index acef91566..c5f56383d 100644 --- a/packages/sdk/src/server/automations/__tests__/announcer.test.ts +++ b/packages/sdk/src/server/automations/__tests__/announcer.test.ts @@ -12,7 +12,6 @@ const { mockGetCommunicationProviderAdapter, mockLoadAutomationThreadFeedbackContext, mockEnqueueTask, - mockGenerateTrackedNonTaskText, mockSlackNotifier, mockAdapterPostMessage, } = vi.hoisted(() => ({ @@ -41,7 +40,6 @@ const { mockGetCommunicationProviderAdapter: vi.fn(), mockLoadAutomationThreadFeedbackContext: vi.fn(), mockEnqueueTask: vi.fn(), - mockGenerateTrackedNonTaskText: vi.fn(), mockSlackNotifier: vi.fn(), mockAdapterPostMessage: vi.fn(), })); @@ -84,11 +82,6 @@ vi.mock('@roomote/cloud-agents/server', () => ({ enqueueTask: mockEnqueueTask, })); -vi.mock('@roomote/cloud-agents/server/non-task-provider-usage', () => ({ - generateTrackedNonTaskText: mockGenerateTrackedNonTaskText, - NON_TASK_INFERENCE_SURFACES: { backgroundAnnouncer: 'background_announcer' }, -})); - vi.mock('@roomote/slack', () => ({ SlackNotifier: mockSlackNotifier, })); diff --git a/packages/sdk/src/server/automations/__tests__/dependabot-triage.test.ts b/packages/sdk/src/server/automations/__tests__/dependabot-triage.test.ts index 5efa940ca..f3f3cba7e 100644 --- a/packages/sdk/src/server/automations/__tests__/dependabot-triage.test.ts +++ b/packages/sdk/src/server/automations/__tests__/dependabot-triage.test.ts @@ -71,7 +71,7 @@ describe('dependabotTriageJob buildScanTask', () => { mockLoadAutomationThreadFeedbackContext.mockResolvedValue(null); }); - it('scopes the scan to environment-backed GitHub repositories and stamps the provider', async () => { + it('scans all active GitHub repositories while keeping remediation environment-backed', async () => { mockGetActiveGitHubRepositoryFullNames.mockResolvedValue([ 'acme/api', 'acme/no-environment', @@ -89,10 +89,15 @@ describe('dependabotTriageJob buildScanTask', () => { throw new Error('expected a scan build'); } + const [payload] = result.payloads; + if (!payload) { + throw new Error('expected a scan payload'); + } + expect(result.payloads).toHaveLength(1); - expect(result.payloads[0]).toMatchObject({ + expect(payload).toMatchObject({ repo: ALL_REPOSITORIES, - selectedRepositories: ['acme/api'], + selectedRepositories: ['acme/api', 'acme/no-environment'], sourceControlProvider: 'github', suggestionSource: 'dependabot_triage', }); @@ -103,9 +108,37 @@ describe('dependabotTriageJob buildScanTask', () => { 'acme/api', 'acme/no-environment', ]); + expect(payload.description).toContain( + 'count its current open Dependabot alerts', + ); + expect(payload.description).toContain('inspect its open pull requests'); + expect(payload.description).toContain( + 'After triage reaches a final result, post exactly one concise status message', + ); + expect(payload.description).toContain( + 'total number of open Dependabot alerts with a critical/high/medium/low severity breakdown', + ); + expect(payload.description).toContain( + 'critical/high/medium/low severity breakdown', + ); + expect(payload.description).toContain( + 'covered by newly started remediation task(s), existing related PRs, or neither', + ); + expect(payload.description).toContain( + 'do not submit duplicate work for alerts it covers', + ); + expect(payload.description).toContain( + 'every eligible environment with uncovered actionable alerts', + ); + expect(payload.description).toContain( + 'Do not post any Slack opening acknowledgement, scan announcement, progress update, or partial finding', + ); + expect(payload.description).toContain( + 'post exactly one concise status message', + ); }); - it('skips when no active GitHub repository is backed by an environment', async () => { + it('scans and reports repositories without environments without permitting remediation launches', async () => { mockGetActiveGitHubRepositoryFullNames.mockResolvedValue([ 'acme/no-environment', ]); @@ -115,10 +148,31 @@ describe('dependabotTriageJob buildScanTask', () => { const result = await config.buildScanTask(buildScanTaskParams()); + expect(result.kind).toBe('scan'); + + if (result.kind !== 'scan') { + throw new Error('expected a scan build'); + } + + expect(result.payloads[0]).toMatchObject({ + selectedRepositories: ['acme/no-environment'], + }); + expect(result.payloads[0]?.description).toContain( + 'Only consider repositories that appear in the "Repository environments" list below', + ); + }); + + it('skips when there are no active GitHub repositories', async () => { + mockGetActiveGitHubRepositoryFullNames.mockResolvedValue([]); + + const result = await config.buildScanTask(buildScanTaskParams()); + expect(result).toEqual({ kind: 'skip', - reason: 'No active GitHub repositories have configured environments', + reason: 'No active GitHub repositories', }); + expect(mockBuildRepositoryCoverage).not.toHaveBeenCalled(); + expect(mockLoadAutomationThreadFeedbackContext).not.toHaveBeenCalled(); }); it('skips when GitHub is not configured', async () => { diff --git a/packages/sdk/src/server/automations/dependabot-triage.ts b/packages/sdk/src/server/automations/dependabot-triage.ts index 53c4c87ca..373e7aa14 100644 --- a/packages/sdk/src/server/automations/dependabot-triage.ts +++ b/packages/sdk/src/server/automations/dependabot-triage.ts @@ -1,7 +1,6 @@ import { buildRepositoryCoverage, formatRepositoryEnvironmentLines, - getEnvironmentBackedCoverage, type RepositoryCoverage, } from '@roomote/cloud-agents/server'; import { ALL_REPOSITORIES } from '@roomote/types'; @@ -42,17 +41,20 @@ function buildDependabotTriagePrompt({ const repositoryEnvironmentSection = repositoryEnvironmentScope ? `\nRepository environments:\n${repositoryEnvironmentScope}\n` : ''; - const followUpInstructions = `If you find actionable candidates, submit up to 3 \`act\` automation work items with \`submit_automation_work_items\`. Do not submit any suggestion work items; they are rejected. Submit at most one work item for each \`targetEnvironmentId\`. Only consider repositories that appear in the "Repository environments" list below. Do not fall back to bare-repo launches. Pick the highest-priority cohesive update bundles across the eligible repositories. Start with the narrowest security fix that is likely to work, but if the alert realistically needs an aligned lockfile refresh, multiple affected workspaces, or a small related dependency bundle, submit that broader cohesive remediation instead of deferring. Do not turn one alert into a broad maintenance sweep. + const followUpInstructions = `For every repository in scope, count its current open Dependabot alerts and inspect its open pull requests for dependency updates or alert references that could already address them. Treat a related open PR as in-flight remediation: do not submit duplicate work for alerts it covers, but include it in the scan summary and identify any alerts it does not cover. + +If you find actionable candidates, submit up to 3 \`act\` automation work items with \`submit_automation_work_items\`. Do not submit any suggestion work items; they are rejected. Submit at most one work item for each \`targetEnvironmentId\`. Only consider repositories that appear in the "Repository environments" list below. Do not fall back to bare-repo launches. Submit one cohesive remediation bundle for every eligible environment with uncovered actionable alerts, in priority order, until the three-item cap is reached. Do not stop after the single highest-priority bundle when other eligible environments have independent uncovered alerts. For a repository with multiple related lockfile or dependency alerts, create one task that aims to resolve every actionable open alert in that cohesive bundle, not only the first alert inspected. Start with the narrowest security fix that is likely to work, but if the alerts realistically need an aligned lockfile refresh, multiple affected workspaces, or a small related dependency bundle, submit that broader cohesive remediation instead of deferring. Do not turn unrelated alerts into a broad maintenance sweep. Each submitted act item must: - target exactly one repository from repository_scope - copy the matching \`targetEnvironmentId\` from the "Repository environments" list - include \`executionPrompt\` that starts with \`$update-dependencies\` -- include investigationContext with the alert URL or number, alert summary, package, ecosystem, manifest path, severity, vulnerable range, first patched version, the exact GitHub CLI commands used during triage, and the validation the execution task must perform before opening a PR +- state that success means re-checking the targeted alert bundle and leaving no actionable alerts from that bundle open, unless an existing related PR already covers them +- include investigationContext with the alert URL or number, alert summary, package, ecosystem, manifest path, severity, vulnerable range, first patched version, related open PRs and the alerts they cover, the exact GitHub CLI commands used during triage, and the validation the execution task must perform before opening a PR -If \`submit_automation_work_items\` succeeds for one or more act items, do not call \`${promptContext.postToolName}\` and do not post a launch announcement. Each execution task starts silently and creates ${promptContext.surfaceLabel} output only later if it needs input, hits a blocker, or finishes with a result. End the task response with a terse internal note that action items were submitted. +Do not post any ${promptContext.surfaceLabel} opening acknowledgement, scan announcement, progress update, or partial finding. After triage reaches a final result, post exactly one concise status message to the configured ${promptContext.surfaceLabel} channel with \`${promptContext.postToolName}\`. State the total number of open Dependabot alerts with a critical/high/medium/low severity breakdown, the per-repository counts, related open PRs (or that none are open), and how many currently open alerts are covered by newly started remediation task(s), existing related PRs, or neither. Keep it manager-readable and do not paste raw GitHub CLI commands, \`gh api\` invocations, or command transcripts. End the task response with a terse internal note after posting the final status. -If there are no actionable alerts, no eligible configured-environment candidates, or no configured environment coverage, do not post to ${promptContext.surfaceLabel}; end with a terse internal note. Treat repository-level gaps such as Dependabot alerts being disabled for a repository, a repository returning zero open alerts, or a repository falling outside configured environment coverage as non-blocking no-op findings for this run, not as GitHub setup/auth blockers worth a ${promptContext.surfaceLabel} post. A clean read-only run is not worth a channel message. Post a concise report to the configured ${promptContext.surfaceLabel} channel with \`${promptContext.postToolName}\` only for GitHub setup/auth blockers (for example missing or suspended Dependabot alert access), so configuration failures do not disappear silently. Keep any such report plain-language and manager-readable, and do not paste the raw GitHub CLI commands, \`gh api\` invocations, or command transcripts into ${promptContext.surfaceLabel}; the exact commands belong only in work item \`investigationContext\`.`; +Treat repository-level gaps such as Dependabot alerts being disabled for a repository, a repository returning zero open alerts, or a repository falling outside configured environment coverage as reportable scan outcomes, not as GitHub setup/auth blockers. If GitHub setup or alert access is blocked, post the same concise status message with the blocker and the counts that could be determined.`; return `$dependabot-triage @@ -88,21 +90,15 @@ export const dependabotTriageJob = createScheduledTriageJob({ // leaves the run's source-control provider ambiguous and GitHub token // minting then fails on the non-GitHub repository names. const selectedRepositories = await getActiveGitHubRepositoryFullNames(); - const repositoryCoverage = - await buildRepositoryCoverage(selectedRepositories); - // Dependabot follow-ups must run validation before opening PRs, so the - // scan only targets repositories backed by a configured environment. - const environmentBackedRepositories = getEnvironmentBackedCoverage( - repositoryCoverage, - ).map((coverage) => coverage.repositoryFullName); - - if (environmentBackedRepositories.length === 0) { + if (selectedRepositories.length === 0) { return { kind: 'skip', - reason: 'No active GitHub repositories have configured environments', + reason: 'No active GitHub repositories', }; } + const repositoryCoverage = + await buildRepositoryCoverage(selectedRepositories); const recentThreadFeedback = await loadAutomationThreadFeedbackContext({ automationKey: 'dependabot_triage', slackChannelId: channelId, @@ -114,12 +110,12 @@ export const dependabotTriageJob = createScheduledTriageJob({ payloads: [ { repo: ALL_REPOSITORIES, - selectedRepositories: environmentBackedRepositories, + selectedRepositories, sourceControlProvider: 'github', description: buildDependabotTriagePrompt({ channelId, destination, - repositoryFullNames: environmentBackedRepositories, + repositoryFullNames: selectedRepositories, repositoryCoverage, manualTrigger, recentThreadFeedback, diff --git a/packages/types/src/__tests__/inference-gateway.test.ts b/packages/types/src/__tests__/inference-gateway.test.ts index 609e48147..64cdb88b3 100644 --- a/packages/types/src/__tests__/inference-gateway.test.ts +++ b/packages/types/src/__tests__/inference-gateway.test.ts @@ -5,9 +5,11 @@ import { getInferenceGatewayProvider, getInferenceGatewayProviderByEnvVarName, INFERENCE_GATEWAY_PROVIDER_ENV_VAR_NAMES, + INFERENCE_GATEWAY_PROVIDERS, isInferenceGatewayCoveredEnvVar, parseInferenceGatewayKeys, } from '../inference-gateway'; +import { getSetupModelProvider } from '../model-provider-config'; describe('inference gateway URL builders', () => { it('appends the gateway path to a platform URL', () => { @@ -52,6 +54,36 @@ describe('inference gateway URL builders', () => { ); }); + it('registers Z.AI providers with v4 bases and empty OpenCode suffix', () => { + const zai = getInferenceGatewayProvider('zai'); + expect(zai).toMatchObject({ + envVarNames: ['ZAI_API_KEY'], + openCodeBaseUrlSuffix: '', + region: { + envVarName: 'ZAI_REGION', + default: 'global', + baseUrls: { + global: 'https://api.z.ai/api/paas/v4', + china: 'https://open.bigmodel.cn/api/paas/v4', + }, + }, + }); + expect(zai?.allowedPaths).toContain('/chat/completions'); + expect(zai?.allowedPaths).not.toContain('/v1/chat/completions'); + expect( + buildInferenceGatewayOpenCodeBaseUrl( + 'https://api.example.com/api/inference', + zai!, + ), + ).toBe('https://api.example.com/api/inference/zai'); + expect(getInferenceGatewayProviderByEnvVarName('ZAI_API_KEY')?.id).toBe( + 'zai', + ); + expect( + getInferenceGatewayProviderByEnvVarName('ZAI_CODING_PLAN_API_KEY')?.id, + ).toBe('zai-coding-plan'); + }); + it('exposes a chatgpt-oauth provider that collapses to the Codex backend', () => { const provider = getInferenceGatewayProvider(CHATGPT_GATEWAY_PROVIDER_ID); expect(provider?.authStrategy).toBe('chatgpt-oauth'); @@ -156,6 +188,29 @@ describe('inference gateway key lookups', () => { ).toBe('https://api.example.com/api/inference/github-copilot'); }); + it('offers exactly the regions its gateway providers hold base URLs for', () => { + const regionProviders = INFERENCE_GATEWAY_PROVIDERS.filter( + (provider) => provider.region?.baseUrls, + ); + + expect(regionProviders.map((provider) => provider.id)).toEqual([ + 'zai', + 'zai-coding-plan', + ]); + + for (const provider of regionProviders) { + const regions = Object.keys(provider.region!.baseUrls!); + + expect(regions).toContain(provider.region!.default); + + const field = ( + getSetupModelProvider(provider.id).additionalEnvFields ?? [] + ).find((entry) => entry.envVarName === provider.region!.envVarName); + + expect(field?.options?.map((option) => option.value)).toEqual(regions); + } + }); + it('parses a comma-separated served-keys value', () => { expect( parseInferenceGatewayKeys('ANTHROPIC_API_KEY, OPENROUTER_API_KEY'), diff --git a/packages/types/src/inference-gateway.ts b/packages/types/src/inference-gateway.ts index 9529a4cdd..dc159a501 100644 --- a/packages/types/src/inference-gateway.ts +++ b/packages/types/src/inference-gateway.ts @@ -123,7 +123,18 @@ export interface InferenceGatewayProvider { * Region resolution for `{region}`-templated upstreams: the deployment env * var to read and the fallback when it is unset. */ - region?: { envVarName: string; default: string }; + region?: { + envVarName: string; + default: string; + /** + * Discrete upstream bases keyed by region env value (e.g. `global` / + * `china`). When set, the resolved region selects a base URL outright + * instead of filling `upstreamBaseUrl`'s `{region}` placeholder, and + * `INFERENCE_GATEWAY_REGION_PATTERN` does not apply. Keeping these under + * `region` is what makes "bases without a region env var" unrepresentable. + */ + baseUrls?: Readonly>; + }; /** How the upstream expects its API key when the gateway forwards. */ authHeader?: InferenceGatewayAuthHeader; /** A configured upstream key is forwarded when present but is not required. */ @@ -167,6 +178,14 @@ const ANTHROPIC_COMPATIBLE_INFERENCE_PATHS: readonly string[] = [ '/v1/models', ]; +/** Paths relative to the models.dev v4 base (not OpenAI `/v1/...`). */ +const ZAI_INFERENCE_PATHS: readonly string[] = [ + '/chat/completions', + '/completions', + '/embeddings', + '/models', +]; + /** * Providers reachable through the inference gateway. HTTP-proxyable providers * whose API-key or OAuth credential can stay on the control plane. Google @@ -314,6 +333,38 @@ export const INFERENCE_GATEWAY_PROVIDERS: readonly InferenceGatewayProvider[] = ], openCodeBaseUrlSuffix: '/v1', }, + { + id: 'zai', + name: 'Z.AI', + envVarNames: ['ZAI_API_KEY'], + authHeader: { name: 'authorization', scheme: 'bearer' }, + allowedPaths: ZAI_INFERENCE_PATHS, + openCodeBaseUrlSuffix: '', + region: { + envVarName: 'ZAI_REGION', + default: 'global', + baseUrls: { + global: 'https://api.z.ai/api/paas/v4', + china: 'https://open.bigmodel.cn/api/paas/v4', + }, + }, + }, + { + id: 'zai-coding-plan', + name: 'Z.AI Coding Plan', + envVarNames: ['ZAI_CODING_PLAN_API_KEY'], + authHeader: { name: 'authorization', scheme: 'bearer' }, + allowedPaths: ZAI_INFERENCE_PATHS, + openCodeBaseUrlSuffix: '', + region: { + envVarName: 'ZAI_CODING_PLAN_REGION', + default: 'global', + baseUrls: { + global: 'https://api.z.ai/api/coding/paas/v4', + china: 'https://open.bigmodel.cn/api/coding/paas/v4', + }, + }, + }, { // GitHub Copilot's OpenCode SDK hits /chat/completions and /responses // under https://api.githubcopilot.com (no /v1 prefix on the base URL). diff --git a/packages/types/src/model-provider-config.test.ts b/packages/types/src/model-provider-config.test.ts index 31d3f4b8a..4846a0959 100644 --- a/packages/types/src/model-provider-config.test.ts +++ b/packages/types/src/model-provider-config.test.ts @@ -230,6 +230,8 @@ describe('SETUP_MODEL_PROVIDER_CATALOG', () => { 'amazon-bedrock', 'google', 'xai', + 'zai', + 'zai-coding-plan', 'github-copilot', 'openai-compatible', 'litellm', @@ -805,6 +807,18 @@ describe('getModelProviderEnvKeyCandidates', () => { ); expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).not.toContain('MISTRAL_API_KEY'); expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).toContain('GEMINI_API_KEY'); + expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).toContain('ZAI_API_KEY'); + expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).toContain('ZAI_REGION'); + expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).toContain( + 'ZAI_CODING_PLAN_API_KEY', + ); + expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).toContain('ZAI_CODING_PLAN_REGION'); + expect(DEFAULT_MODEL_PROVIDER_CREDENTIAL_ENV_VAR_NAMES).toContain( + 'ZAI_API_KEY', + ); + expect(DEFAULT_MODEL_PROVIDER_CREDENTIAL_ENV_VAR_NAMES).not.toContain( + 'ZAI_REGION', + ); expect(DEFAULT_MODEL_PROVIDER_ENV_KEYS).not.toContain('GITHUB_TOKEN'); // Ambient AWS access keys are intentionally NOT forwarded by default so a // controller's own infrastructure credentials never leak into sandboxes; @@ -1273,4 +1287,42 @@ describe('collectSetupModelProviderCredentialValues', () => { }), ).toThrow('Anthropic does not accept a ANTHROPIC_API_KEY value.'); }); + + it('accepts a listed option value for selectable fields', () => { + const zaiProvider = SETUP_MODEL_PROVIDER_CATALOG.find( + (provider) => provider.id === 'zai', + )!; + + expect( + collectSetupModelProviderCredentialValues({ + provider: zaiProvider, + apiKey: 'zai-key', + additionalEnvValues: { ZAI_REGION: 'china' }, + isEnvVarSatisfied: () => false, + action: 'save it', + }), + ).toEqual({ + values: [ + { name: 'ZAI_API_KEY', value: 'zai-key' }, + { name: 'ZAI_REGION', value: 'china' }, + ], + clearedEnvVarNames: [], + }); + }); + + it('rejects a value not in options for selectable fields', () => { + const zaiProvider = SETUP_MODEL_PROVIDER_CATALOG.find( + (provider) => provider.id === 'zai', + )!; + + expect(() => + collectSetupModelProviderCredentialValues({ + provider: zaiProvider, + apiKey: 'zai-key', + additionalEnvValues: { ZAI_REGION: 'us-east-1' }, + isEnvVarSatisfied: () => false, + action: 'save it', + }), + ).toThrow('Enter a valid Region for Z.AI to save it.'); + }); }); diff --git a/packages/types/src/model-provider-config.ts b/packages/types/src/model-provider-config.ts index 8d2e0c118..17fb8fc46 100644 --- a/packages/types/src/model-provider-config.ts +++ b/packages/types/src/model-provider-config.ts @@ -114,8 +114,20 @@ export type SetupModelProviderEnvField = { secret: boolean; required: boolean; placeholder?: string; + /** When set, connect UIs render a select; values must match an option. */ + options?: readonly { value: string; label: string }[]; }; +/** + * Region values must stay in step with the `region.baseUrls` keys on the + * matching inference gateway provider; the gateway has no base URL for a + * region this list offers. `inference-gateway.test.ts` asserts the pairing. + */ +export const ZAI_REGION_OPTIONS = [ + { value: 'global', label: 'International' }, + { value: 'china', label: 'China' }, +] as const; + export type SetupModelProviderDescriptor = { id: SetupModelProviderId; label: string; @@ -508,6 +520,61 @@ export const SETUP_MODEL_PROVIDER_CATALOG = [ 'grok-4-5': 'xai/grok-4.5', }), }, + { + id: 'zai', + label: 'Z.AI', + envVarName: 'ZAI_API_KEY', + defaultRoomoteModel: 'zai/glm-5.2', + authKind: 'api-key', + credentialHelp: { + text: 'Paste a platform API key for the selected region. International keys come from the Z.AI API console; China keys come from the Zhipu / BigModel console. Coding Plan membership keys belong on Z.AI Coding Plan, not here.', + href: 'https://z.ai/manage-apikey/apikey-list', + linkLabel: 'Open Z.AI API keys', + }, + additionalEnvFields: [ + { + envVarName: 'ZAI_REGION', + label: 'Region', + secret: false, + required: true, + options: ZAI_REGION_OPTIONS, + }, + ], + suggestedTaskModels: mapRecommendedTaskModels({ + 'glm-5-2': 'zai/glm-5.2', + }), + recommendedRoleModels: { + vision: 'zai/glm-5v-turbo', + }, + }, + { + id: 'zai-coding-plan', + label: 'Z.AI Coding Plan', + envVarName: 'ZAI_CODING_PLAN_API_KEY', + envVarLabel: 'Z.AI Coding Plan API key', + defaultRoomoteModel: 'zai-coding-plan/glm-5.2', + authKind: 'api-key', + credentialHelp: { + text: 'Paste a Coding Plan API key for the selected region. Do not use a general platform API key here.', + href: 'https://docs.z.ai/devpack/overview', + linkLabel: 'Open Z.AI Coding Plan docs', + }, + additionalEnvFields: [ + { + envVarName: 'ZAI_CODING_PLAN_REGION', + label: 'Region', + secret: false, + required: true, + options: ZAI_REGION_OPTIONS, + }, + ], + suggestedTaskModels: mapRecommendedTaskModels({ + 'glm-5-2': 'zai-coding-plan/glm-5.2', + }), + recommendedRoleModels: { + vision: 'zai-coding-plan/glm-5v-turbo', + }, + }, { // Provider id matches models.dev / OpenCode (`github-copilot`). // Connections use OpenCode's GitHub device-code OAuth flow. @@ -656,6 +723,30 @@ export function getSetupModelProviderAdditionalEnvFields(provider: { return provider.additionalEnvFields ?? []; } +/** + * Fills in the default for every selectable field that has no value yet. A + * select always shows one of its options, so connect UIs must submit that + * option rather than an empty string; existing values are left untouched. + */ +export function getDefaultAdditionalEnvValues( + fields: readonly SetupModelProviderEnvField[], + values: Record = {}, +): Record { + const seeded = { ...values }; + + for (const field of fields) { + if ( + field.options && + field.options.length > 0 && + !seeded[field.envVarName]?.trim() + ) { + seeded[field.envVarName] = field.options[0]!.value; + } + } + + return seeded; +} + function getSetupModelProviderRequiredEnvVarNames( provider: Pick< SetupModelProviderDescriptor, @@ -1334,6 +1425,14 @@ export function collectSetupModelProviderCredentialValues(options: { const value = submittedValue?.trim() ?? ''; if (value) { + if (field.options && field.options.length > 0) { + const allowed = new Set(field.options.map((option) => option.value)); + if (!allowed.has(value)) { + throw new Error( + `Enter a valid ${field.label} for ${provider.label} to ${options.action}.`, + ); + } + } values.push({ name: field.envVarName, value }); } else if (field.required) { if (!options.isEnvVarSatisfied(field.envVarName)) { diff --git a/packages/types/src/task-models.ts b/packages/types/src/task-models.ts index bf91c1375..456debf11 100644 --- a/packages/types/src/task-models.ts +++ b/packages/types/src/task-models.ts @@ -30,6 +30,8 @@ export const ENABLED_DIRECT_TASK_MODEL_PROVIDER_IDS = [ 'amazon-bedrock', 'google', 'xai', + 'zai', + 'zai-coding-plan', 'github-copilot', 'openai-compatible', 'litellm', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b458f2753..6fccf6635 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ overrides: '@vercel/routing-utils>ajv': 6.14.0 '@vercel/routing-utils>path-to-regexp': 6.3.0 unicorn-magic: 0.2.0 - dompurify: 3.4.11 + dompurify: 3.4.12 esbuild: ^0.28.1 linkify-it: 5.0.2 engine.io>ws: 8.21.0 @@ -27,16 +27,16 @@ overrides: better-auth>zod: ^4.3.6 '@better-auth/core>zod': ^4.3.6 js-cookie: 3.0.7 - js-yaml: 4.2.0 + js-yaml: 4.3.0 gray-matter>js-yaml: 3.15.0 read-yaml-file@1>js-yaml: 3.15.0 nise>path-to-regexp: 8.4.0 - protobufjs: 7.6.3 + protobufjs: 7.6.5 qs: 6.15.2 router>path-to-regexp: 8.4.0 socket.io>socket.io-adapter: 2.5.8 socket.io-adapter>ws: 8.21.0 - tar: 7.5.20 + tar: 7.5.21 zod: ^3.25.76 astro>zod: ^4.3.6 knip>zod: ^4.1.11 @@ -53,6 +53,15 @@ overrides: tsx: 4.20.4 webpack: 5.104.1 yaml-language-server>yaml: 2.8.3 + '@opentelemetry/propagator-jaeger': 2.9.0 + body-parser: 2.3.0 + brace-expansion@<1.1.16: 1.1.16 + brace-expansion@>=2.0.0 <2.1.2: 2.1.2 + brace-expansion@>=3.0.0 <5.0.8: 5.0.8 + fast-uri: 3.1.4 + immutable: 5.1.8 + postcss: 8.5.18 + sharp: 0.35.0 packageExtensionsChecksum: sha256-JfWrBLcTGUbHJ+9MRdfpiV99a/JYeeS1zDFUVoCXsM8= @@ -121,8 +130,8 @@ importers: specifier: 3.1000.0 version: 3.1000.0 '@hono/node-server': - specifier: 1.19.13 - version: 1.19.13(hono@4.12.27) + specifier: 2.0.5 + version: 2.0.5(hono@4.12.27) '@hono/trpc-server': specifier: ^0.4.2 version: 0.4.2(@trpc/server@11.15.0(typescript@5.9.3))(hono@4.12.27) @@ -225,7 +234,7 @@ importers: version: 24.12.4 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -242,8 +251,8 @@ importers: specifier: ^6.16.4 version: 6.16.4 '@hono/node-server': - specifier: 1.19.13 - version: 1.19.13(hono@4.12.27) + specifier: 2.0.5 + version: 2.0.5(hono@4.12.27) '@roomote/cloud-agents': specifier: workspace:^ version: link:../../packages/cloud-agents @@ -304,7 +313,7 @@ importers: version: 24.12.4 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -353,7 +362,7 @@ importers: version: 24.12.4 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -436,7 +445,7 @@ importers: version: 6.1.2 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.13.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -496,7 +505,7 @@ importers: version: 7.1.4 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -510,8 +519,8 @@ importers: specifier: 3.1000.0 version: 3.1000.0 '@better-auth/drizzle-adapter': - specifier: ^1.6.19 - version: 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8)) + specifier: ^1.6.22 + version: 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8)) '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.60.0(react@19.2.4)) @@ -634,7 +643,7 @@ importers: version: link:../../packages/types '@sentry/nextjs': specifier: ^10.56.0 - version: 10.56.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + version: 10.56.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@streamdown/cjk': specifier: ^1.0.2 version: 1.0.2(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.4)(unified@11.0.5) @@ -681,8 +690,8 @@ importers: specifier: ^6.2.6 version: 6.2.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) better-auth: - specifier: ^1.6.19 - version: 1.6.19(@opentelemetry/api@1.9.1)(better-sqlite3@11.10.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.15.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))) + specifier: ^1.6.22 + version: 1.6.22(@opentelemetry/api@1.9.1)(better-sqlite3@11.10.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.15.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))) better-sse: specifier: ^0.16.1 version: 0.16.1 @@ -815,13 +824,13 @@ importers: version: 10.2.10(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6)) '@storybook/addon-docs': specifier: ^10.2.10 - version: 10.2.10(@types/react@19.2.10)(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + version: 10.2.10(@types/react@19.2.10)(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@storybook/addon-themes': specifier: ^10.2.10 version: 10.2.10(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6)) '@storybook/nextjs-vite': specifier: ^10.2.10 - version: 10.2.10(@babel/core@7.29.7)(esbuild@0.28.1)(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + version: 10.2.10(@babel/core@7.29.7)(esbuild@0.28.1)(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -853,11 +862,11 @@ importers: specifier: ^26.1.0 version: 26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) postcss: - specifier: ^8.5.6 - version: 8.5.14 + specifier: 8.5.18 + version: 8.5.18 postcss-load-config: specifier: ^6.0.1 - version: 6.0.1(jiti@2.7.0)(postcss@8.5.14)(tsx@4.20.4)(yaml@2.9.0) + version: 6.0.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(yaml@2.9.0) storybook: specifier: ^10.2.10 version: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) @@ -872,13 +881,13 @@ importers: version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) webpack: specifier: 5.104.1 - version: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14) + version: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18) apps/worker: dependencies: '@hono/node-server': - specifier: 1.19.13 - version: 1.19.13(hono@4.12.27) + specifier: 2.0.5 + version: 2.0.5(hono@4.12.27) '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) @@ -922,8 +931,8 @@ importers: specifier: ^14.0.2 version: 14.0.3 dompurify: - specifier: 3.4.11 - version: 3.4.11 + specifier: 3.4.12 + version: 3.4.12 execa: specifier: 9.6.1 version: 9.6.1 @@ -996,7 +1005,7 @@ importers: version: 8.18.1 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0) vitest: specifier: ^4.1.1 version: 4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) @@ -1286,7 +1295,7 @@ importers: version: link:../types drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8) + version: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8) nanoid: specifier: ^5.1.16 version: 5.1.16 @@ -2144,14 +2153,14 @@ packages: '@balena/dockerignore@1.0.2': resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} - '@better-auth/core@1.6.19': - resolution: {integrity: sha512-ddE3Y9MoQ8t32QSO5Y8mV7pmnDAv5LdJjX1SPWUH6JUUmuOc7YEy3B5JfXZIJbRaXFdnAitN7pHPVa5u/dYAZA==} + '@better-auth/core@1.6.22': + resolution: {integrity: sha512-aFH/5nzmR501jAJPKjJfiVg4BrkcjVCqq9WS9JnhTruE/2PIWopv1QGMiRIRqxXaPbHczri7cqRdhep3Lg5PMw==} peerDependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@cloudflare/workers-types': '>=4' '@opentelemetry/api': ^1.9.0 - better-call: 1.3.6 + better-call: 1.3.7 jose: ^6.1.0 kysely: ^0.28.5 || ^0.29.0 nanostores: ^1.0.1 @@ -2161,46 +2170,46 @@ packages: '@opentelemetry/api': optional: true - '@better-auth/drizzle-adapter@1.6.19': - resolution: {integrity: sha512-57C9ePorPmIEez6dHuQMz3hCTkYim0lfVRIoRtX7PiVfiRFB2bjXseQwrCJfQmkgMFlkp1s/c9nKgAjc2EvAIg==} + '@better-auth/drizzle-adapter@1.6.22': + resolution: {integrity: sha512-uNa9qH53CfxBmuKP8kbLWxY90oIRwUPn5BcHhO+szK05e2yh6EYwSNNivDSqV3YG5HPfjtPHulklInjq1wtm3w==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 drizzle-orm: ^0.45.2 peerDependenciesMeta: drizzle-orm: optional: true - '@better-auth/kysely-adapter@1.6.19': - resolution: {integrity: sha512-DlmvllEd0nv8JL+plX3JB3WTmqDFnGFOmjmIiUDHo8R3PTAvC0ZaJq3Jk+LQLN5PyVQSUzXZKtvTQYaqRHzBaw==} + '@better-auth/kysely-adapter@1.6.22': + resolution: {integrity: sha512-4k/07lPRizlQi+B+uOE5CwTfH3w+Lq8ZDX1nDN1+e+glRVKAIfHoLvC9cfAVcCbio3DDrl0RTbwjLwjEhG0LxA==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 kysely: ^0.28.17 || ^0.29.0 peerDependenciesMeta: kysely: optional: true - '@better-auth/memory-adapter@1.6.19': - resolution: {integrity: sha512-cZ8iLRG/T8Oi/CqE9FTHj3z8pIOqRsINi50trWxPNwyY/Eyb7YCljrBi0PuqgIdyVs7BWfrrtEYTpO4ddfuwEw==} + '@better-auth/memory-adapter@1.6.22': + resolution: {integrity: sha512-rbepe/gHhWs0aF4fAu6+l+wNPJxT9XN4U+Hqa1Y/5HhjtT9y5evo3INrSWlkIOymsMaQ0cBPrSL5pm9Z195hcA==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.19': - resolution: {integrity: sha512-8AReXqhMGiGQIPEpGbmAhh+R4g70TsAVvzwdd6Aj4q+LTSwd3tqC89TFJ4eX8KSplxm9PFBZ6g6gsRmDd7urQg==} + '@better-auth/mongo-adapter@1.6.22': + resolution: {integrity: sha512-OYnfySHlVkIx7y6XNBsCHjKhl6IYGprRkFwifc/TAuPBVjoRKhLKRAXuzMdWTQYFt4FYb6holbhjNUrXxPIWcw==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 mongodb: ^6.0.0 || ^7.0.0 peerDependenciesMeta: mongodb: optional: true - '@better-auth/prisma-adapter@1.6.19': - resolution: {integrity: sha512-pXZBhR7/bzJb48IUHlGMyz9SM9h1OCO5GIIuHEllJYt8MKgrjtsnXfUkwZh6pAUEIp3WxBEYMMK96bfkqHiWEg==} + '@better-auth/prisma-adapter@1.6.22': + resolution: {integrity: sha512-I6lWQwLva732V600u5dLM2kRcQ94pRZOVVfZ87+Ow9RBxMUnV+I+YQ5h2yggdN2tmsmITacZTy1DSZbDxGu0LQ==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -2210,10 +2219,10 @@ packages: prisma: optional: true - '@better-auth/telemetry@1.6.19': - resolution: {integrity: sha512-bBaB6SMIsrD3WutDdm5YQ1bQyinANTimHD8RtpLWhNh/jIXvzgwVVCrDFqA256vcGC/ZRCydmtW8ZrUqNMW9Og==} + '@better-auth/telemetry@1.6.22': + resolution: {integrity: sha512-glq/oEk9qP+zGh9k/WUH5+pwvBCMolNNhaAVBCtYQrkADFee2gP3VoPs1YeO9coNuOmBhc+AYSIHs+fL9DoJnw==} peerDependencies: - '@better-auth/core': ^1.6.19 + '@better-auth/core': ^1.6.22 '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 @@ -2745,6 +2754,12 @@ packages: peerDependencies: hono: ^4 + '@hono/node-server@2.0.5': + resolution: {integrity: sha512-yQFvDmyDo3y6rEOJZDUYPJ49DIKTPpIk4kGvm40xx4Ejne0Pu9a1+exxPN+C1UppWK/WGZX9F++/Xs231tE86g==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + '@hono/trpc-server@0.4.2': resolution: {integrity: sha512-3TDrc42CZLgcTFkXQba+y7JlRWRiyw1AqhLqztWyNS2IFT+3bHld0lxKdGBttCtGKHYx0505dM67RMazjhdZqw==} engines: {node: '>=16.0.0'} @@ -2790,152 +2805,161 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.0': + resolution: {integrity: sha512-ZgaYEwaj+lx/5n4W8GmZ2IYz0PQHjN5eqRcfijWGB+2Aq7ZInZGa0qJyAn6DEtyLuWHRSrmWOqT9q3qqTBvmUQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.0': + resolution: {integrity: sha512-c1z9LFpKB0slQW3RchwBE8iSVzGp70TNjUUO9k4BZwwW4HH7JBGHeIy4b+kk4n/kcBASb9evKCE3/7Slmslgiw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.0': + resolution: {integrity: sha512-Li2KTev0H90kEtnJHkI9xQojXt1AqWmFBMXiPw5kqd1jQgP7gi5HVK/qC5Rmh/59NuAwUuPzzPITmX22NomYYQ==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.0': + resolution: {integrity: sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.0': + resolution: {integrity: sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.0': + resolution: {integrity: sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.0': + resolution: {integrity: sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.0': + resolution: {integrity: sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.0': + resolution: {integrity: sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.0': + resolution: {integrity: sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.0': + resolution: {integrity: sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.0': + resolution: {integrity: sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.0': + resolution: {integrity: sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.0': + resolution: {integrity: sha512-4+4XHLNT5wDT0roYlHTEmH9lDKt0acf9Tv+3hM3iceOirkxrR404/3WjAYZ9F9CkHrxeRcGLJXbi4vluMZ9O+A==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.0': + resolution: {integrity: sha512-VVlpEWwizEFIOom0zdoeKuO5nuTswzVE5uHcBNvHzmeHUpNFajY3HFfbQ+zIH4E2kVaZ/yVxmsShW56TtEy4uA==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.0': + resolution: {integrity: sha512-N3hzbEpUTJC8pWpPVJvgzGxM+so/MAXc8O2s/53B0LL9ZGpfXpME7Wizkc5d/8fRBlBtkDjzoZGDCqqNDHqLEw==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.0': + resolution: {integrity: sha512-l6vmKVPnbS0RhVMbyxP5meAARsbhCnBN4fy31qz0+3a6Rv4jEqfzDrT89y6ZPkCi0AJGnwp2En528yXo401Hpw==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.0': + resolution: {integrity: sha512-MYlMiPFiv/EKPAHnp3yNZ9AAWFsxga9c5Bkc6wkar6bqzHLlkGVJHRm0u1ei+VXnZxp3Mz9MG9ZIsI8vSOf3sQ==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.0': + resolution: {integrity: sha512-TYaItB5oj1ioXjhyn2xrR208vf+YuIIcHptQWRRaBmFhvIvL9D72DXN8w75xup0KXA8UdEAhQ9Qb2S49FD/9Cw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.0': + resolution: {integrity: sha512-DSTb6ijQzqe6DdAaOBVqJ/SYf1vO8EW5bK6X6LRXufEBebf2722VCdvBUtZ3rtV0x2ApfPNDy/p7LrrjaWjiyQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.0': + resolution: {integrity: sha512-K7ykQ+26Rt6+4BTU80AuGgTPIYX86UxiAKT4rcXX/WNTo7k1ZxpKz+TguHnwVpCqQK3B5PK0vZ0ZBe6nz/ib1w==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.0': + resolution: {integrity: sha512-9woLIFORERCr+6cWu87dQ22J34EExkhc73U1kZW0c+RclQqWetoodByp4dWZ/hN8/KVmTRAx2HOnUwib8AwZdA==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.0': + resolution: {integrity: sha512-t+kie1TOyaDM6Dho+f+y0VqIUNhYQaKCUahuZVi0E0frgdiaOaPsDxDW3wfKacUdaNBCnK/ZDBMg33ydvHj8uA==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.0': + resolution: {integrity: sha512-M5eKxug0dabbaWgFKvPa3odNs2OpaP+81NASfGKkt4GcYXpNhSu7CaeYxWkLNV6vHmUp4hnCxnxrUyhUJhXbKA==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.0': + resolution: {integrity: sha512-z0+pZ03QCDvdVN0Ez9IX/yjWC19ikMlXrmdYMwYNLTh2BLPx3hXWPvyqWfquZ0BTO9O6GVOjIVoTcyyacMnWlQ==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.0': + resolution: {integrity: sha512-feNnlz5ZHKr0MY1LPHvZQyJeBkbo4ctsn0D8FvA53VTw5TC63rfEL2UrWbkSBR19htSE7Mw78xYVwdJqoMWVHw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -3628,8 +3652,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@2.7.1': - resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} + '@opentelemetry/propagator-jaeger@2.9.0': + resolution: {integrity: sha512-4mYGty27rYvSM0jtp1ZUOqd3LfVRCYg9H5G9OFzSx5HViYToU21MFhWfco7x1HwXr7ER8yGOiCIHZUwjPksc0Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3706,6 +3730,10 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.41.2': resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4090,9 +4118,6 @@ packages: '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.2': - resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} - '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -6566,8 +6591,8 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - better-auth@1.6.19: - resolution: {integrity: sha512-68eXWKj0sxa0xW4+n4tENd6Co94UCynPKe1fncmO6kIB3XhSXWgwDEpiUouJV2dmLBrHM1FPkoI6Q5597zCGpQ==} + better-auth@1.6.22: + resolution: {integrity: sha512-B5s6+lPsDWp8rGLRnvNyr5h9tftG9zLRjNrlkEJdYRhcuhPhJiw9b8o6ibgxEFpSAdUqoDaP5/FLqfu8QsXIVg==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -6628,8 +6653,8 @@ packages: vue: optional: true - better-call@1.3.6: - resolution: {integrity: sha512-no1jI+h6Bkxs1NVBo4rONbVIzsPjZ8IUu7IHaJBiFwVX1XEQGN8KpHots5fSWmXe9nNyLuLIcgx6WEUcE6EDaA==} + better-call@1.3.7: + resolution: {integrity: sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==} peerDependencies: zod: ^3.25.76 peerDependenciesMeta: @@ -6666,22 +6691,22 @@ packages: bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.15: - resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -6994,6 +7019,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -7383,8 +7412,8 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - dompurify@3.4.11: - resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} @@ -7865,8 +7894,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} fast-xml-builder@1.2.0: resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} @@ -8090,8 +8119,8 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - giget@3.3.0: - resolution: {integrity: sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==} + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} hasBin: true github-from-package@0.0.0: @@ -8334,8 +8363,8 @@ packages: immer@11.1.4: resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} - immutable@5.1.5: - resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + immutable@5.1.8: + resolution: {integrity: sha512-TM5YqrGeTsVIPPpILzeqZ8D2Zc2TvNgSDi88zPF2a4cyqQdWV/wVWBDRDbNzzrLeRWScrFcOX9lW2iX6GOtUDw==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -8640,6 +8669,9 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.2.4: + resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -8654,8 +8686,8 @@ packages: resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsdom@26.1.0: @@ -8759,8 +8791,8 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - kysely@0.29.2: - resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} + kysely@0.29.4: + resolution: {integrity: sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA==} engines: {node: '>=22.0.0'} layout-base@1.0.2: @@ -9388,8 +9420,8 @@ packages: engines: {node: ^18 || >=20} hasBin: true - nanostores@1.3.0: - resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} + nanostores@1.4.1: + resolution: {integrity: sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q==} engines: {node: ^20.0.0 || >=22.0.0} napi-build-utils@2.0.0: @@ -9900,7 +9932,7 @@ packages: engines: {node: '>= 18'} peerDependencies: jiti: '>=1.21.0' - postcss: '>=8.0.9' + postcss: 8.5.18 tsx: 4.20.4 yaml: ^2.4.2 peerDependenciesMeta: @@ -9913,12 +9945,8 @@ packages: yaml: optional: true - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.18: + resolution: {integrity: sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -9993,8 +10021,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - protobufjs@7.6.3: - resolution: {integrity: sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==} + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} proxy-addr@2.0.7: @@ -10488,8 +10516,8 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -10509,9 +10537,9 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.0: + resolution: {integrity: sha512-BqvG5XbwPZ4NV0DK90d86leEECMsoa8bO0nqnKWlBDYxri4GJ7c4EDInaF6q20lTh/mATmnDIKWJFfXnoVfH5g==} + engines: {node: '>=20.9.0'} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -10871,8 +10899,8 @@ packages: tar-stream@3.2.0: resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} - tar@7.5.20: - resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + tar@7.5.21: + resolution: {integrity: sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==} engines: {node: '>=18'} teex@1.0.1: @@ -11069,7 +11097,7 @@ packages: peerDependencies: '@microsoft/api-extractor': ^7.36.0 '@swc/core': ^1 - postcss: ^8.4.12 + postcss: 8.5.18 typescript: '>=4.5.0' peerDependenciesMeta: '@microsoft/api-extractor': @@ -11119,6 +11147,10 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -11632,6 +11664,9 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zustand@5.0.10: resolution: {integrity: sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==} engines: {node: '>=12.20.0'} @@ -12515,52 +12550,52 @@ snapshots: '@balena/dockerignore@1.0.2': {} - '@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0)': + '@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1)': dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.43.0 '@standard-schema/spec': 1.1.0 - better-call: 1.3.6(zod@4.3.6) - jose: 6.2.3 - kysely: 0.29.2 - nanostores: 1.3.0 - zod: 4.3.6 + better-call: 1.3.7(zod@4.4.3) + jose: 6.2.4 + kysely: 0.29.4 + nanostores: 1.4.1 + zod: 4.4.3 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8))': + '@better-auth/drizzle-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8))': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 optionalDependencies: - drizzle-orm: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8) + drizzle-orm: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8) - '@better-auth/kysely-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)': + '@better-auth/kysely-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(kysely@0.29.4)': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 optionalDependencies: - kysely: 0.29.2 + kysely: 0.29.4 - '@better-auth/memory-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + '@better-auth/memory-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + '@better-auth/mongo-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + '@better-auth/prisma-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 - '@better-auth/telemetry@1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + '@better-auth/telemetry@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 @@ -12745,7 +12780,7 @@ snapshots: '@changesets/parse@0.4.3': dependencies: '@changesets/types': 6.1.0 - js-yaml: 4.2.0 + js-yaml: 4.3.0 '@changesets/pre@2.0.2': dependencies: @@ -12857,7 +12892,7 @@ snapshots: isomorphic-ws: 5.0.0(ws@8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) pathe: 2.0.3 shell-quote: 1.9.0 - tar: 7.5.20 + tar: 7.5.21 transitivePeerDependencies: - debug - supports-color @@ -13089,7 +13124,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.2.0 + js-yaml: 4.3.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -13146,14 +13181,14 @@ snapshots: dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.6.3 + protobufjs: 7.6.5 yargs: 17.7.2 '@grpc/proto-loader@0.8.0': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.6.3 + protobufjs: 7.6.5 yargs: 17.7.2 '@hey-api/client-fetch@0.10.2(@hey-api/openapi-ts@0.99.0(typescript@5.9.3))': @@ -13173,7 +13208,7 @@ snapshots: dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 - js-yaml: 4.2.0 + js-yaml: 4.3.0 '@hey-api/openapi-ts@0.99.0(typescript@5.9.3)': dependencies: @@ -13214,6 +13249,10 @@ snapshots: dependencies: hono: 4.12.27 + '@hono/node-server@2.0.5(hono@4.12.27)': + dependencies: + hono: 4.12.27 + '@hono/trpc-server@0.4.2(@trpc/server@11.15.0(typescript@5.9.3))(hono@4.12.27)': dependencies: '@trpc/server': 11.15.0(typescript@5.9.3) @@ -13257,98 +13296,108 @@ snapshots: '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.0': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.0 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.0': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-freebsd-wasm32@0.35.0': + dependencies: + '@img/sharp-wasm32': 0.35.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.0': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.0': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.0': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.0': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.0': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.0': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.0': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.3.0': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.0 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.0 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.0 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.0 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.0 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.0 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.0': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.0 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.35.0': dependencies: '@emnapi/runtime': 1.11.2 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.0': + dependencies: + '@img/sharp-wasm32': 0.35.0 optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-arm64@0.35.0': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-ia32@0.35.0': + optional: true + + '@img/sharp-win32-x64@0.35.0': optional: true '@inquirer/external-editor@1.0.3(@types/node@24.12.4)': @@ -13475,7 +13524,7 @@ snapshots: '@melloware/react-logviewer@6.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: hotkeys-js: 4.0.2 - immutable: 5.1.5 + immutable: 5.1.8 mitt: 3.0.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -14150,14 +14199,14 @@ snapshots: '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - protobufjs: 7.6.3 + protobufjs: 7.6.5 '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-jaeger@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) @@ -14217,7 +14266,7 @@ snapshots: '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) @@ -14259,6 +14308,8 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} + '@opentelemetry/semantic-conventions@1.43.0': {} + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14465,8 +14516,6 @@ snapshots: '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.2': {} - '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} @@ -15286,7 +15335,7 @@ snapshots: '@sentry/core@10.56.0': {} - '@sentry/nextjs@10.56.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@sentry/nextjs@10.56.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 @@ -15298,7 +15347,7 @@ snapshots: '@sentry/opentelemetry': 10.56.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) '@sentry/react': 10.56.0(react@19.2.4) '@sentry/vercel-edge': 10.56.0 - '@sentry/webpack-plugin': 5.3.0(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@sentry/webpack-plugin': 5.3.0(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) next: 16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) rollup: 4.61.1 stacktrace-parser: 0.1.11 @@ -15431,10 +15480,10 @@ snapshots: '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@sentry/core': 10.56.0 - '@sentry/webpack-plugin@5.3.0(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@sentry/webpack-plugin@5.3.0(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: '@sentry/bundler-plugin-core': 5.3.0 - webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14) + webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18) transitivePeerDependencies: - encoding - supports-color @@ -15830,10 +15879,10 @@ snapshots: axe-core: 4.11.0 storybook: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) - '@storybook/addon-docs@10.2.10(@types/react@19.2.10)(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@storybook/addon-docs@10.2.10(@types/react@19.2.10)(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.10)(react@19.2.4) - '@storybook/csf-plugin': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@storybook/csf-plugin': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@storybook/react-dom-shim': 10.2.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6)) react: 19.2.4 @@ -15852,9 +15901,9 @@ snapshots: storybook: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@storybook/builder-vite@10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: - '@storybook/csf-plugin': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@storybook/csf-plugin': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) storybook: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) ts-dedent: 2.2.0 vite: 7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0) @@ -15863,7 +15912,7 @@ snapshots: - rollup - webpack - '@storybook/csf-plugin@10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@storybook/csf-plugin@10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: storybook: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) unplugin: 2.3.10 @@ -15871,7 +15920,7 @@ snapshots: esbuild: 0.28.1 rollup: 4.61.1 vite: 7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0) - webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14) + webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18) '@storybook/global@5.0.0': {} @@ -15880,11 +15929,11 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/nextjs-vite@10.2.10(@babel/core@7.29.7)(esbuild@0.28.1)(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@storybook/nextjs-vite@10.2.10(@babel/core@7.29.7)(esbuild@0.28.1)(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: - '@storybook/builder-vite': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@storybook/builder-vite': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@storybook/react': 10.2.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3) - '@storybook/react-vite': 10.2.10(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@storybook/react-vite': 10.2.10(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) next: 16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -15908,11 +15957,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6) - '@storybook/react-vite@10.2.10(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14))': + '@storybook/react-vite@10.2.10(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0)) '@rollup/pluginutils': 5.3.0(rollup@4.61.1) - '@storybook/builder-vite': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + '@storybook/builder-vite': 10.2.10(esbuild@0.28.1)(rollup@4.61.1)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) '@storybook/react': 10.2.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.10(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(prettier@3.8.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(utf-8-validate@6.0.6))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -16060,7 +16109,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.3.1 '@tailwindcss/oxide': 4.3.1 - postcss: 8.5.15 + postcss: 8.5.18 tailwindcss: 4.3.1 '@tanstack/query-core@5.90.20': {} @@ -16862,7 +16911,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -17120,29 +17169,29 @@ snapshots: before-after-hook@4.0.0: {} - better-auth@1.6.19(@opentelemetry/api@1.9.1)(better-sqlite3@11.10.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.15.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))): + better-auth@1.6.22(@opentelemetry/api@1.9.1)(better-sqlite3@11.10.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8))(next@16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.15.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.1(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.20.4)(yaml@2.9.0))): dependencies: - '@better-auth/core': 1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8)) - '@better-auth/kysely-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2) - '@better-auth/telemetry': 1.6.19(@better-auth/core@1.6.19(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@3.25.76))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/drizzle-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8)) + '@better-auth/kysely-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(kysely@0.29.4) + '@better-auth/memory-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@3.25.76))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.2.0 '@noble/hashes': 2.2.0 - better-call: 1.3.6(zod@4.3.6) + better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 - jose: 6.2.3 - kysely: 0.29.2 - nanostores: 1.3.0 - zod: 4.3.6 + jose: 6.2.4 + kysely: 0.29.4 + nanostores: 1.4.1 + zod: 4.4.3 optionalDependencies: better-sqlite3: 11.10.0 drizzle-kit: 0.31.9 - drizzle-orm: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8) + drizzle-orm: 0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8) next: 16.2.11(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) pg: 8.15.6 react: 19.2.4 @@ -17152,14 +17201,14 @@ snapshots: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-call@1.3.6(zod@4.3.6): + better-call@1.3.7(zod@4.4.3): dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 rou3: 0.7.12 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.1.2 optionalDependencies: - zod: 4.3.6 + zod: 4.4.3 better-path-resolve@1.0.0: dependencies: @@ -17192,32 +17241,32 @@ snapshots: bn.js@4.12.3: {} - body-parser@2.2.2: + body-parser@2.3.0: dependencies: bytes: 3.1.2 - content-type: 1.0.5 + content-type: 2.0.0 debug: 4.4.3 http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 qs: 6.15.2 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color bowser@2.14.1: {} - brace-expansion@1.1.15: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.3: + brace-expansion@2.1.2: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.6: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -17295,7 +17344,7 @@ snapshots: defu: 6.1.7 dotenv: 17.4.2 exsolve: 1.1.0 - giget: 3.3.0 + giget: 3.3.1 jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 @@ -17511,6 +17560,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.2.2: {} @@ -17915,7 +17966,7 @@ snapshots: '@grpc/grpc-js': 1.14.4 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.7 - protobufjs: 7.6.3 + protobufjs: 7.6.5 tar-fs: 2.1.4 transitivePeerDependencies: - supports-color @@ -17932,7 +17983,7 @@ snapshots: dom-accessibility-api@0.6.3: {} - dompurify@3.4.11: + dompurify@3.4.12: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -17953,14 +18004,14 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.2)(pg@8.15.6)(postgres@3.4.8): + drizzle-orm@0.45.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.1)(@types/pg@8.16.0)(better-sqlite3@11.10.0)(kysely@0.29.4)(pg@8.15.6)(postgres@3.4.8): optionalDependencies: '@electric-sql/pglite': 0.3.0 '@libsql/client-wasm': 0.15.5 '@opentelemetry/api': 1.9.1 '@types/pg': 8.16.0 better-sqlite3: 11.10.0 - kysely: 0.29.2 + kysely: 0.29.4 pg: 8.15.6 postgres: 3.4.8 @@ -17985,7 +18036,7 @@ snapshots: glob: 13.0.6 openapi-fetch: 0.14.1 platform: 1.3.6 - tar: 7.5.20 + tar: 7.5.21 undici: 7.28.0 ecdsa-sig-formatter@1.0.11: @@ -18435,7 +18486,7 @@ snapshots: express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.2 + body-parser: 2.3.0 content-disposition: 1.0.1 content-type: 1.0.5 cookie: 0.7.2 @@ -18509,7 +18560,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.2: {} + fast-uri@3.1.4: {} fast-xml-builder@1.2.0: dependencies: @@ -18752,7 +18803,7 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - giget@3.3.0: {} + giget@3.3.1: {} github-from-package@0.0.0: optional: true @@ -19083,7 +19134,7 @@ snapshots: immer@11.1.4: {} - immutable@5.1.5: {} + immutable@5.1.8: {} import-fresh@3.3.1: dependencies: @@ -19374,6 +19425,8 @@ snapshots: jose@6.2.3: {} + jose@6.2.4: {} + joycon@3.1.1: {} js-base64@3.9.1: @@ -19386,7 +19439,7 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.2.0: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -19506,7 +19559,7 @@ snapshots: fast-glob: 3.3.3 formatly: 0.3.0 jiti: 2.6.1 - js-yaml: 4.2.0 + js-yaml: 4.3.0 minimist: 1.2.8 oxc-resolver: 11.17.1 picocolors: 1.1.1 @@ -19520,7 +19573,7 @@ snapshots: kuler@2.0.0: {} - kysely@0.29.2: {} + kysely@0.29.4: {} layout-base@1.0.2: {} @@ -19952,7 +20005,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.4.11 + dompurify: 3.4.12 es-toolkit: 1.47.0 katex: 0.16.28 khroma: 2.1.0 @@ -20304,19 +20357,19 @@ snapshots: minimatch@10.2.4: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.8 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.15 + brace-expansion: 1.1.16 minimatch@5.1.8: dependencies: - brace-expansion: 2.0.3 + brace-expansion: 2.1.2 minimatch@9.0.7: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.8 minimist@1.2.8: {} @@ -20342,7 +20395,7 @@ snapshots: cbor-x: 1.6.4 long: 5.3.2 nice-grpc: 2.1.14 - protobufjs: 7.6.3 + protobufjs: 7.6.5 smol-toml: 1.6.1 uuid: 11.1.1 @@ -20406,7 +20459,7 @@ snapshots: nanoid@5.1.16: {} - nanostores@1.3.0: {} + nanostores@1.4.1: {} napi-build-utils@2.0.0: optional: true @@ -20442,7 +20495,7 @@ snapshots: '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.11.1 caniuse-lite: 1.0.30001806 - postcss: 8.5.14 + postcss: 8.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.4) @@ -20456,7 +20509,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.2.11 '@next/swc-win32-x64-msvc': 16.2.11 '@opentelemetry/api': 1.9.1 - sharp: 0.34.5 + sharp: 0.35.0 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -20977,31 +21030,16 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.14)(tsx@4.20.4)(yaml@2.9.0): + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.7.0 - postcss: 8.5.14 + postcss: 8.5.18 tsx: 4.20.4 yaml: 2.9.0 - postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(yaml@2.9.0): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - jiti: 2.7.0 - postcss: 8.5.15 - tsx: 4.20.4 - yaml: 2.9.0 - - postcss@8.5.14: - dependencies: - nanoid: 3.3.15 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.15: + postcss@8.5.18: dependencies: nanoid: 3.3.15 picocolors: 1.1.1 @@ -21070,7 +21108,7 @@ snapshots: property-information@7.1.0: {} - protobufjs@7.6.3: + protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 @@ -21078,7 +21116,6 @@ snapshots: '@protobufjs/eventemitter': 1.1.1 '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.2 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.1 @@ -21729,7 +21766,7 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@3.1.0: {} + set-cookie-parser@3.1.2: {} set-function-length@1.2.2: dependencies: @@ -21757,36 +21794,37 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.34.5: + sharp@0.35.0: dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.35.0 + '@img/sharp-darwin-x64': 0.35.0 + '@img/sharp-freebsd-wasm32': 0.35.0 + '@img/sharp-libvips-darwin-arm64': 1.3.0 + '@img/sharp-libvips-darwin-x64': 1.3.0 + '@img/sharp-libvips-linux-arm': 1.3.0 + '@img/sharp-libvips-linux-arm64': 1.3.0 + '@img/sharp-libvips-linux-ppc64': 1.3.0 + '@img/sharp-libvips-linux-riscv64': 1.3.0 + '@img/sharp-libvips-linux-s390x': 1.3.0 + '@img/sharp-libvips-linux-x64': 1.3.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 + '@img/sharp-libvips-linuxmusl-x64': 1.3.0 + '@img/sharp-linux-arm': 0.35.0 + '@img/sharp-linux-arm64': 0.35.0 + '@img/sharp-linux-ppc64': 0.35.0 + '@img/sharp-linux-riscv64': 0.35.0 + '@img/sharp-linux-s390x': 0.35.0 + '@img/sharp-linux-x64': 0.35.0 + '@img/sharp-linuxmusl-arm64': 0.35.0 + '@img/sharp-linuxmusl-x64': 0.35.0 + '@img/sharp-webcontainers-wasm32': 0.35.0 + '@img/sharp-win32-arm64': 0.35.0 + '@img/sharp-win32-ia32': 0.35.0 + '@img/sharp-win32-x64': 0.35.0 optional: true shebang-command@2.0.0: @@ -22266,7 +22304,7 @@ snapshots: - bare-buffer - react-native-b4a - tar@7.5.20: + tar@7.5.21: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -22283,17 +22321,17 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.6.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)): + terser-webpack-plugin@5.6.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.48.0 - webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14) + webpack: 5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18) optionalDependencies: esbuild: 0.28.1 lightningcss: 1.32.0 - postcss: 8.5.14 + postcss: 8.5.18 terser@5.48.0: dependencies: @@ -22401,7 +22439,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(typescript@5.9.3)(yaml@2.9.0): dependencies: bundle-require: 5.1.0(esbuild@0.28.1) cac: 6.7.14 @@ -22412,7 +22450,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.20.4)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.18)(tsx@4.20.4)(yaml@2.9.0) resolve-from: 5.0.0 rollup: 4.60.0 source-map: 0.7.6 @@ -22421,7 +22459,7 @@ snapshots: tinyglobby: 0.2.17 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.15 + postcss: 8.5.18 typescript: 5.9.3 transitivePeerDependencies: - jiti @@ -22470,6 +22508,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -22738,7 +22782,7 @@ snapshots: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.14 + postcss: 8.5.18 rollup: 4.61.1 tinyglobby: 0.2.17 optionalDependencies: @@ -22755,7 +22799,7 @@ snapshots: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.14 + postcss: 8.5.18 rollup: 4.61.1 tinyglobby: 0.2.17 optionalDependencies: @@ -22884,7 +22928,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14): + webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -22908,7 +22952,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.14)) + terser-webpack-plugin: 5.6.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)(webpack@5.104.1(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.18)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -23097,6 +23141,8 @@ snapshots: zod@4.3.6: {} + zod@4.4.3: {} + zustand@5.0.10(@types/react@19.2.10)(immer@11.1.4)(react@19.2.4)(use-sync-external-store@1.5.0(react@19.2.4)): optionalDependencies: '@types/react': 19.2.10