diff --git a/apps/web/src/app/(app)/claw/components/ClawOnboardingFakeWalkthrough.tsx b/apps/web/src/app/(app)/claw/components/ClawOnboardingFakeWalkthrough.tsx index 3354912b07..1cdd0c5e04 100644 --- a/apps/web/src/app/(app)/claw/components/ClawOnboardingFakeWalkthrough.tsx +++ b/apps/web/src/app/(app)/claw/components/ClawOnboardingFakeWalkthrough.tsx @@ -67,7 +67,6 @@ const fakeStatus = { botVibe: 'Focused, capable, effective', botEmoji: '🤖', workerUrl: 'https://claw.kilo.ai', - controllerCapabilitiesVersion: null, name: 'Fake KiloClaw', instanceId: 'fake-instance', inboundEmailAddress: null, diff --git a/apps/web/src/app/(app)/claw/components/ClawOnboardingFlow.state.test.ts b/apps/web/src/app/(app)/claw/components/ClawOnboardingFlow.state.test.ts index dc5d7f1805..f2f729fe2a 100644 --- a/apps/web/src/app/(app)/claw/components/ClawOnboardingFlow.state.test.ts +++ b/apps/web/src/app/(app)/claw/components/ClawOnboardingFlow.state.test.ts @@ -48,7 +48,6 @@ function createStatus(status: KiloClawDashboardStatus['status']): KiloClawDashbo botVibe: null, botEmoji: null, workerUrl: 'https://claw.kilo.ai', - controllerCapabilitiesVersion: null, instanceId: null, inboundEmailAddress: null, inboundEmailEnabled: false, diff --git a/apps/web/src/app/(app)/claw/components/withStatusQueryBoundary.test.ts b/apps/web/src/app/(app)/claw/components/withStatusQueryBoundary.test.ts index ebc7df3245..9c478097fa 100644 --- a/apps/web/src/app/(app)/claw/components/withStatusQueryBoundary.test.ts +++ b/apps/web/src/app/(app)/claw/components/withStatusQueryBoundary.test.ts @@ -41,7 +41,6 @@ const baseStatus: KiloClawDashboardStatus = { botVibe: null, botEmoji: null, workerUrl: 'https://claw.kilo.ai', - controllerCapabilitiesVersion: null, instanceId: null, inboundEmailAddress: 'amber-river-quiet-maple@kiloclaw.ai', inboundEmailEnabled: true, diff --git a/apps/web/src/app/(app)/claw/new/ClawNewClient.state.test.ts b/apps/web/src/app/(app)/claw/new/ClawNewClient.state.test.ts index 7d7316a41c..e64aaa12db 100644 --- a/apps/web/src/app/(app)/claw/new/ClawNewClient.state.test.ts +++ b/apps/web/src/app/(app)/claw/new/ClawNewClient.state.test.ts @@ -39,7 +39,6 @@ const baseStatus: KiloClawDashboardStatus = { botVibe: null, botEmoji: null, workerUrl: 'https://claw.kilo.ai', - controllerCapabilitiesVersion: null, instanceId: 'instance-1', inboundEmailAddress: 'amber-river-quiet-maple@kiloclaw.ai', inboundEmailEnabled: true, diff --git a/apps/web/src/lib/config.server.test.ts b/apps/web/src/lib/config.server.test.ts deleted file mode 100644 index cf53dd347d..0000000000 --- a/apps/web/src/lib/config.server.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, it, expect } from '@jest/globals'; -import { resolveInstanceUrlTemplate } from './config.server'; - -describe('resolveInstanceUrlTemplate', () => { - describe('kill switch (KILOCLAW_INSTANCE_URL_TEMPLATE=legacy)', () => { - it('returns empty (legacy routing) when set to the kill-switch sentinel in production', () => { - expect(resolveInstanceUrlTemplate('legacy', 'production', 'https://claw.kilo.ai')).toBe(''); - }); - - it('matches the sentinel case-insensitively', () => { - expect(resolveInstanceUrlTemplate('Legacy', 'production', 'https://claw.kilo.ai')).toBe(''); - expect(resolveInstanceUrlTemplate('LEGACY', 'production', 'https://claw.kilo.ai')).toBe(''); - }); - - it('also disables per-instance URLs in dev when set', () => { - expect(resolveInstanceUrlTemplate('legacy', 'development', 'http://localhost:8795')).toBe(''); - }); - - it('treats an explicit empty string as "unset" (falls through to defaults), not as a kill switch', () => { - // Platform env pipelines often coerce empty values to "unset", so - // empty string must not be the rollback signal. - expect(resolveInstanceUrlTemplate('', 'production', 'https://claw.kilo.ai')).toBe( - 'https://{label}.kiloclaw.ai' - ); - expect(resolveInstanceUrlTemplate('', 'development', 'http://localhost:8795')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - }); - - describe('production defaults', () => { - it('defaults to the canonical prod template when no override is set', () => { - expect(resolveInstanceUrlTemplate(undefined, 'production', 'https://claw.kilo.ai')).toBe( - 'https://{label}.kiloclaw.ai' - ); - }); - - it('honors an explicit override in production', () => { - expect( - resolveInstanceUrlTemplate( - 'https://{label}.preview.kiloclaw.ai', - 'production', - 'https://claw.kilo.ai' - ) - ).toBe('https://{label}.preview.kiloclaw.ai'); - }); - }); - - describe('development / test defaults', () => { - it('derives a loopback-parity template from a localhost KILOCLAW_API_URL', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', 'http://localhost:8795')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('derives a loopback-parity template from a 127.0.0.1 KILOCLAW_API_URL', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', 'http://127.0.0.1:8795')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('preserves the port from KILOCLAW_API_URL when non-default', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', 'http://localhost:9999')).toBe( - 'http://{label}.kiloclaw.localhost:9999' - ); - }); - - it('preserves the scheme from KILOCLAW_API_URL', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', 'https://localhost:8795')).toBe( - 'https://{label}.kiloclaw.localhost:8795' - ); - }); - - it('falls back to the wrangler dev port when KILOCLAW_API_URL is missing', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', undefined)).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('falls back when KILOCLAW_API_URL is unparsable', () => { - expect(resolveInstanceUrlTemplate(undefined, 'development', 'not a url')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('uses the fallback template when KILOCLAW_API_URL points at a non-loopback host', () => { - // Remote staging — dev mode with a non-local worker. We don't try - // to derive a wildcard host for it; fall back to the loopback - // template. Operators who want a real per-instance URL on remote - // staging set KILOCLAW_INSTANCE_URL_TEMPLATE explicitly. - expect(resolveInstanceUrlTemplate(undefined, 'development', 'https://staging.kilo.ai')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('defaults loopback-parity in test mode too', () => { - expect(resolveInstanceUrlTemplate(undefined, 'test', 'http://localhost:8795')).toBe( - 'http://{label}.kiloclaw.localhost:8795' - ); - }); - - it('honors a dev-parity override', () => { - expect( - resolveInstanceUrlTemplate( - 'http://{label}.kiloclaw.localhost:8795', - 'development', - 'http://localhost:8795' - ) - ).toBe('http://{label}.kiloclaw.localhost:8795'); - }); - }); -}); diff --git a/apps/web/src/lib/config.server.ts b/apps/web/src/lib/config.server.ts index a7ccad9d6f..f3bb4df35f 100644 --- a/apps/web/src/lib/config.server.ts +++ b/apps/web/src/lib/config.server.ts @@ -209,96 +209,6 @@ export const KILOCLAW_INTERNAL_API_SECRET = getEnvVariable('KILOCLAW_INTERNAL_AP export const KILOCLAW_INBOUND_EMAIL_DOMAIN = getEnvVariable('KILOCLAW_INBOUND_EMAIL_DOMAIN') || 'kiloclaw.ai'; -/** - * Per-instance worker URL template. - * - * Per-instance URLs are the default in BOTH production and dev/test so a - * merge of the name-based routing feature flips them on automatically, - * without forcing anyone to edit env files. - * - * Resolution rules (checked in order): - * 1. `KILOCLAW_INSTANCE_URL_TEMPLATE=legacy` (case-insensitive) is the - * explicit **kill switch** — disables per-instance URLs entirely and - * falls back to the single-host `KILOCLAW_API_URL`. Operators can - * roll prod back without a code deploy; devs can disable locally. - * A non-empty sentinel is used (rather than empty string) because - * Vercel / Node env pipelines often coerce empty env entries into - * "unset", making an empty-string rollback unreliable. - * 2. A non-empty `KILOCLAW_INSTANCE_URL_TEMPLATE` is used verbatim. - * Must contain `{label}`; missing placeholder is a misconfiguration - * warned about at render time (see `workerUrlForInstance`). - * 3. Otherwise in `NODE_ENV=production`, default to the canonical - * `https://{label}.kiloclaw.ai` template. - * 4. Otherwise (dev/test) derive a template from `KILOCLAW_API_URL`: - * if `KILOCLAW_API_URL` looks like a loopback URL (`http://localhost:` - * / `http://127.0.0.1:`), emit - * `http://{label}.kiloclaw.localhost:` so the browser - * auto-resolves `*.kiloclaw.localhost` to `127.0.0.1` per RFC 6761. - * If `KILOCLAW_API_URL` is missing or unparsable, fall back to the - * same template with the wrangler dev port (`8795`) — matches - * `.dev.vars.example`. - * - * When the template ends up set and contains `{label}`, `getStatus` - * emits a `workerUrl` pointing at the instance's own virtual host - * (derived from its sandboxId) for instances whose - * `controllerCapabilitiesVersion >= 2`. Pre-v2 instances keep falling - * back to `KILOCLAW_API_URL`. - * - * Exported as a plain function so it's testable without forcing a - * re-import of this entire module (which triggers production-only - * validation of unrelated secrets). - */ -const DEFAULT_DEV_WRANGLER_PORT = '8795'; - -/** - * Sentinel value for `KILOCLAW_INSTANCE_URL_TEMPLATE` that disables the - * per-instance URL pattern entirely. Case-insensitive match. Picked as - * a non-empty word because empty env values are unreliable across - * Vercel / Node / dotenv pipelines (often dropped or indistinguishable - * from "unset"), which would mean the kill switch silently fails open. - */ -const KILL_SWITCH_SENTINEL = 'legacy'; - -function deriveDevTemplateFromWorkerUrl(workerUrl: string | undefined): string { - const fallback = `http://{label}.kiloclaw.localhost:${DEFAULT_DEV_WRANGLER_PORT}`; - if (!workerUrl) return fallback; - try { - const parsed = new URL(workerUrl); - // Only derive when we're pointed at a loopback dev worker. Anything - // else (remote staging, preview domains, etc.) uses the same - // fallback — operators can still override explicitly. - if (parsed.hostname !== 'localhost' && parsed.hostname !== '127.0.0.1') { - return fallback; - } - const port = parsed.port || DEFAULT_DEV_WRANGLER_PORT; - return `${parsed.protocol}//{label}.kiloclaw.localhost:${port}`; - } catch { - return fallback; - } -} - -export function resolveInstanceUrlTemplate( - envVar: string | undefined, - nodeEnv: string | undefined, - workerUrl: string | undefined -): string { - // Explicit kill switch. Empty string falls through to the production - // / dev defaults — operators must set `legacy` to disable, not "". - if (envVar !== undefined && envVar.toLowerCase() === KILL_SWITCH_SENTINEL) { - return ''; - } - // Non-empty explicit override wins. - if (envVar !== undefined && envVar !== '') return envVar; - if (nodeEnv === 'production') return 'https://{label}.kiloclaw.ai'; - return deriveDevTemplateFromWorkerUrl(workerUrl); -} - -export const KILOCLAW_INSTANCE_URL_TEMPLATE = resolveInstanceUrlTemplate( - process.env.KILOCLAW_INSTANCE_URL_TEMPLATE, - process.env.NODE_ENV, - KILOCLAW_API_URL -); - // KiloClaw Early Bird Checkout export const STRIPE_KILOCLAW_EARLYBIRD_PRICE_ID = getEnvVariable( 'STRIPE_KILOCLAW_EARLYBIRD_PRICE_ID' diff --git a/apps/web/src/lib/kiloclaw/instance-url.test.ts b/apps/web/src/lib/kiloclaw/instance-url.test.ts deleted file mode 100644 index 3cc97697fd..0000000000 --- a/apps/web/src/lib/kiloclaw/instance-url.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, it, expect, jest } from '@jest/globals'; -import { workerUrlForInstance } from './instance-url'; -import { sandboxIdFromUserId, sandboxIdFromInstanceId } from '@kilocode/worker-utils/sandbox-id'; - -const LEGACY = 'https://claw.kilo.ai'; -const TEMPLATE = 'https://{label}.kiloclaw.ai'; - -describe('workerUrlForInstance', () => { - it('falls back to the legacy URL when the template is unset', () => { - const sandboxId = sandboxIdFromInstanceId('550e8400-e29b-41d4-a716-446655440000'); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: '', - fallback: LEGACY, - }) - ).toBe(LEGACY); - }); - - it('falls back to the legacy URL and warns once when the template has no {label} placeholder', () => { - const sandboxId = sandboxIdFromInstanceId('550e8400-e29b-41d4-a716-446655440000'); - const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); - try { - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: 'https://claw.kiloclaw.ai', - fallback: LEGACY, - }) - ).toBe(LEGACY); - // Subsequent calls with the same misconfiguration must not spam logs. - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: 'https://claw.kiloclaw.ai', - fallback: LEGACY, - }); - expect(warn).toHaveBeenCalledTimes(1); - expect(warn.mock.calls[0][0]).toMatch(/missing the \{label\} placeholder/); - } finally { - warn.mockRestore(); - } - }); - - it('falls back to the legacy URL for pre-v2 instances', () => { - const sandboxId = sandboxIdFromInstanceId('550e8400-e29b-41d4-a716-446655440000'); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: null, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toBe(LEGACY); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 1, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toBe(LEGACY); - }); - - it('falls back to the legacy URL when sandboxId is null (no-instance sentinel)', () => { - expect( - workerUrlForInstance({ - sandboxId: null, - controllerCapabilitiesVersion: 2, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toBe(LEGACY); - }); - - it('expands the template for instance-keyed sandboxIds on v2+', () => { - const sandboxId = sandboxIdFromInstanceId('550e8400-e29b-41d4-a716-446655440000'); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toBe('https://i-550e8400e29b41d4a716446655440000.kiloclaw.ai'); - }); - - it('expands the template for legacy userId sandboxes on v2+', () => { - const sandboxId = sandboxIdFromUserId('oauth/google:118234567890'); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toMatch(/^https:\/\/u-[0-9a-v]+\.kiloclaw\.ai$/); - }); - - it('falls back to the legacy URL when the sandboxId cannot be safely labelled', () => { - const overlongSandboxId = sandboxIdFromUserId('a'.repeat(39)); - expect( - workerUrlForInstance({ - sandboxId: overlongSandboxId, - controllerCapabilitiesVersion: 2, - template: TEMPLATE, - fallback: LEGACY, - }) - ).toBe(LEGACY); - }); - - it('uses the hardcoded default when fallback is empty', () => { - expect( - workerUrlForInstance({ - sandboxId: null, - controllerCapabilitiesVersion: 2, - template: '', - fallback: '', - }) - ).toBe('https://claw.kilo.ai'); - }); - - it('works with dev-parity templates (http + port)', () => { - const sandboxId = sandboxIdFromInstanceId('550e8400-e29b-41d4-a716-446655440000'); - expect( - workerUrlForInstance({ - sandboxId, - controllerCapabilitiesVersion: 2, - template: 'http://{label}.kiloclaw.localhost:8795', - fallback: 'http://localhost:8795', - }) - ).toBe('http://i-550e8400e29b41d4a716446655440000.kiloclaw.localhost:8795'); - }); -}); diff --git a/apps/web/src/lib/kiloclaw/instance-url.ts b/apps/web/src/lib/kiloclaw/instance-url.ts deleted file mode 100644 index 8cd01e3026..0000000000 --- a/apps/web/src/lib/kiloclaw/instance-url.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Per-instance worker URL minting. - * - * Returns the dashboard-facing URL the browser should use to talk to a - * specific KiloClaw instance. - * - * When `KILOCLAW_INSTANCE_URL_TEMPLATE` is set (e.g. - * `https://{label}.kiloclaw.ai`) AND the instance is on the post-PR1 - * controller contract (`controllerCapabilitiesVersion >= 2`), the - * template is expanded with the sandboxId's hostname label. Otherwise - * falls back to the legacy single-host `KILOCLAW_API_URL`. - * - * The capability gate matters: v1 machines don't have the per-instance - * origin in their OpenClaw allowlist, so WebSocket upgrades from the - * per-instance host would fail openclaw's exact-match origin check. - * Keeping v1 instances on the legacy host until they restart onto v2 - * avoids a user-visible regression. - * - * Inputs: - * - `sandboxId`: DO's authoritative sandboxId (null for no-instance sentinel) - * - `controllerCapabilitiesVersion`: from the worker's `getStatus` - * (null → treat as pre-v1, legacy host) - * - `template`: `KILOCLAW_INSTANCE_URL_TEMPLATE` (empty → legacy host) - * - `fallback`: `KILOCLAW_API_URL` (empty → "https://claw.kilo.ai") - */ - -import { hostnameLabelFromSandboxId } from '@kilocode/worker-utils/hostname-label'; - -const MIN_CAPABILITY_VERSION_FOR_PER_INSTANCE_URL = 2; - -const DEFAULT_LEGACY_URL = 'https://claw.kilo.ai'; - -/** - * Process-local guard so the "misconfigured template" warning fires - * once per worker/Node process instead of on every getStatus call. - * Resets on cold start, which is the right granularity for operator - * feedback after a config change. - */ -let warnedAboutMissingLabelPlaceholder = false; - -export function workerUrlForInstance(params: { - sandboxId: string | null; - controllerCapabilitiesVersion: number | null; - template: string; - fallback: string; -}): string { - const { sandboxId, controllerCapabilitiesVersion, template, fallback } = params; - const legacyUrl = fallback || DEFAULT_LEGACY_URL; - - if (!template) return legacyUrl; - if (!template.includes('{label}')) { - // Operator set a template but forgot the placeholder. Silently - // falling back to the legacy URL hides the misconfiguration; emit - // a one-time warning so it shows up in logs. - if (!warnedAboutMissingLabelPlaceholder) { - warnedAboutMissingLabelPlaceholder = true; - console.warn( - '[workerUrlForInstance] KILOCLAW_INSTANCE_URL_TEMPLATE is set but missing the {label} placeholder; falling back to legacy URL' - ); - } - return legacyUrl; - } - if (!sandboxId) return legacyUrl; - if ((controllerCapabilitiesVersion ?? 0) < MIN_CAPABILITY_VERSION_FOR_PER_INSTANCE_URL) { - return legacyUrl; - } - const label = hostnameLabelFromSandboxId(sandboxId); - if (!label) return legacyUrl; - return template.replace('{label}', label); -} diff --git a/apps/web/src/lib/kiloclaw/types.ts b/apps/web/src/lib/kiloclaw/types.ts index 8006ba0cf7..fe0a6fb343 100644 --- a/apps/web/src/lib/kiloclaw/types.ts +++ b/apps/web/src/lib/kiloclaw/types.ts @@ -225,15 +225,6 @@ export type PlatformStatusResponse = { botNature: string | null; botVibe: string | null; botEmoji: string | null; - /** - * Version of the controller-configuration contract the running machine - * was started with. Bumped by the worker whenever the set of env vars / - * config it writes into a machine changes in a way callers care about. - * `null` means the instance has never been started under a versioned - * contract (treat as pre-v1 / legacy). See - * `services/kiloclaw/src/config.ts` (`WORKER_CONTROLLER_CAPABILITIES_VERSION`). - */ - controllerCapabilitiesVersion: number | null; }; /** A single registry DO's entries + migration status. */ @@ -603,14 +594,7 @@ export type ChatCredentials = { /** Combined status returned by tRPC getStatus */ export type KiloClawDashboardStatus = PlatformStatusResponse & { - /** - * Worker base URL for constructing the "Open" link. - * - * When `KILOCLAW_INSTANCE_URL_TEMPLATE` is configured and the instance - * is on `controllerCapabilitiesVersion >= 2`, this is the per-instance - * virtual host (e.g. `https://i-.kiloclaw.ai`). Otherwise it falls - * back to `KILOCLAW_API_URL` (production: `https://claw.kilo.ai`). - */ + /** Worker base URL for constructing the "Open" link. Falls back to claw.kilo.ai. */ workerUrl: string; name: string | null; /** Postgres row ID. Used to construct /i/{instanceId} proxy paths for instance-keyed instances. */ diff --git a/apps/web/src/routers/kiloclaw-router.ts b/apps/web/src/routers/kiloclaw-router.ts index a9276d9515..c5dc52f5a1 100644 --- a/apps/web/src/routers/kiloclaw-router.ts +++ b/apps/web/src/routers/kiloclaw-router.ts @@ -17,8 +17,7 @@ import { isValidCustomSecretKey, isValidConfigPath, } from '@kilocode/kiloclaw-secret-catalog'; -import { KILOCLAW_API_URL, KILOCLAW_INSTANCE_URL_TEMPLATE } from '@/lib/config.server'; -import { workerUrlForInstance } from '@/lib/kiloclaw/instance-url'; +import { KILOCLAW_API_URL } from '@/lib/config.server'; import { db, type DrizzleTransaction } from '@/lib/drizzle'; import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db'; import { @@ -915,7 +914,6 @@ function createNoInstanceStatus(userId: string, workerUrl: string): KiloClawDash botVibe: null, botEmoji: null, workerUrl, - controllerCapabilitiesVersion: null, name: null, instanceId: null, inboundEmailAddress: null, @@ -2347,13 +2345,10 @@ export const kiloclawRouter = createTRPCRouter({ getStatus: baseProcedure.query(async ({ ctx }) => { const instance = await getActiveInstance(ctx.user.id); - const legacyWorkerUrl = KILOCLAW_API_URL || 'https://claw.kilo.ai'; + const workerUrl = KILOCLAW_API_URL || 'https://claw.kilo.ai'; if (!instance) { - // No instance yet → no sandboxId yet → per-instance URL can't be - // minted. Serve the legacy host; the real host kicks in once the - // dashboard provisions and re-fetches status. - return createNoInstanceStatus(ctx.user.id, legacyWorkerUrl); + return createNoInstanceStatus(ctx.user.id, workerUrl); } const client = new KiloClawInternalClient(); @@ -2362,13 +2357,6 @@ export const kiloclawRouter = createTRPCRouter({ getInboundEmailAddressForInstance(instance.id), ]); - const workerUrl = workerUrlForInstance({ - sandboxId: status.sandboxId, - controllerCapabilitiesVersion: status.controllerCapabilitiesVersion, - template: KILOCLAW_INSTANCE_URL_TEMPLATE, - fallback: legacyWorkerUrl, - }); - return { ...status, name: instance.name ?? null, diff --git a/apps/web/src/routers/organizations/organization-kiloclaw-router.ts b/apps/web/src/routers/organizations/organization-kiloclaw-router.ts index f30e970ac2..2f42d95671 100644 --- a/apps/web/src/routers/organizations/organization-kiloclaw-router.ts +++ b/apps/web/src/routers/organizations/organization-kiloclaw-router.ts @@ -17,8 +17,7 @@ import { isValidCustomSecretKey, isValidConfigPath, } from '@kilocode/kiloclaw-secret-catalog'; -import { KILOCLAW_API_URL, KILOCLAW_INSTANCE_URL_TEMPLATE } from '@/lib/config.server'; -import { workerUrlForInstance } from '@/lib/kiloclaw/instance-url'; +import { KILOCLAW_API_URL } from '@/lib/config.server'; import { sentryLogger } from '@/lib/utils.server'; import { db } from '@/lib/drizzle'; import { @@ -310,7 +309,7 @@ export const organizationKiloclawRouter = createTRPCRouter({ getStatus: organizationMemberProcedure.query(async ({ ctx, input }) => { const instance = await getActiveOrgInstance(ctx.user.id, input.organizationId); - const legacyWorkerUrl = KILOCLAW_API_URL || 'https://claw.kilo.ai'; + const workerUrl = KILOCLAW_API_URL || 'https://claw.kilo.ai'; // No org instance → return a "no instance" sentinel so the frontend // renders setup entry points. Without this guard, workerInstanceId(null) @@ -352,8 +351,7 @@ export const organizationKiloclawRouter = createTRPCRouter({ botNature: null, botVibe: null, botEmoji: null, - workerUrl: legacyWorkerUrl, - controllerCapabilitiesVersion: null, + workerUrl, name: null, instanceId: null, inboundEmailAddress: null, @@ -367,13 +365,6 @@ export const organizationKiloclawRouter = createTRPCRouter({ getInboundEmailAddressForInstance(instance.id), ]); - const workerUrl = workerUrlForInstance({ - sandboxId: status.sandboxId, - controllerCapabilitiesVersion: status.controllerCapabilitiesVersion, - template: KILOCLAW_INSTANCE_URL_TEMPLATE, - fallback: legacyWorkerUrl, - }); - return { ...status, name: instance.name ?? null, diff --git a/packages/worker-utils/package.json b/packages/worker-utils/package.json index 8523d8263a..5a9540a46b 100644 --- a/packages/worker-utils/package.json +++ b/packages/worker-utils/package.json @@ -6,8 +6,6 @@ "exports": { ".": "./src/index.ts", "./instance-id": "./src/instance-id.ts", - "./sandbox-id": "./src/sandbox-id.ts", - "./hostname-label": "./src/hostname-label.ts", "./redact-headers": "./src/redact-headers.ts", "./kiloclaw-billing-observability": "./src/kiloclaw-billing-observability.ts" }, diff --git a/packages/worker-utils/src/hostname-label.ts b/packages/worker-utils/src/hostname-label.ts deleted file mode 100644 index 4f5a59d727..0000000000 --- a/packages/worker-utils/src/hostname-label.ts +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Hostname label <-> sandboxId translation for per-instance virtual hosting - * on `*.kiloclaw.ai` (or a configured dev-suffix). - * - * Two instance shapes map to two label prefixes: - * - * instance-keyed sandboxId "ki_{32hex}" <-> "i-{32hex}" - * legacy sandboxId base64url(userId) <-> "u-{base32hex(userId)}" - * - * Prefix disambiguates the two cases without a database lookup. - * - * The per-instance URL used to inject per-instance origins into - * `OPENCLAW_ALLOWED_ORIGINS` and (post-PR2) to route incoming requests by - * `Host` is built from two env-configurable pieces: - * - * KILOCLAW_INSTANCE_HOST_SUFFIX default ".kiloclaw.ai" - * KILOCLAW_INSTANCE_URL_SCHEME default "https" - * - * Dev parity: set the suffix to `.kiloclaw.localhost:8795` and the scheme to - * `http` and the worker will both inject `http://