diff --git a/dev/local/env-sync/parse.ts b/dev/local/env-sync/parse.ts index 886f192a6..ec8b67738 100644 --- a/dev/local/env-sync/parse.ts +++ b/dev/local/env-sync/parse.ts @@ -223,8 +223,15 @@ function resolveAnnotatedValue( const isOrigins = key.includes('ORIGINS'); const isHostname = key.includes('HOSTNAME') && !key.includes('URL'); const isWs = key.includes('_WS_'); - // LAN IP for container services, but never for ORIGINS keys - const host = serviceUsesLanIp && !isOrigins && lanIp ? lanIp : 'localhost'; + const defaultUsesDockerHost = entry.defaultValue.includes('host.docker.internal'); + // LAN IP for container services, but never for ORIGINS keys. + // Preserve host.docker.internal when the example default uses it + // (sandbox containers need it to reach the host from inside Docker). + const host = defaultUsesDockerHost + ? 'host.docker.internal' + : serviceUsesLanIp && !isOrigins && lanIp + ? lanIp + : 'localhost'; const protocol = isWs ? 'ws' : 'http'; const resolvedParts: string[] = []; diff --git a/dev/local/env-sync/plan.test.ts b/dev/local/env-sync/plan.test.ts index f3984e37b..8bfc39f0b 100644 --- a/dev/local/env-sync/plan.test.ts +++ b/dev/local/env-sync/plan.test.ts @@ -348,3 +348,39 @@ test('keeps .env.local values ahead of wrangler vars for local overrides', () => repo.cleanup(); } }); + +test('preserves host.docker.internal in @url defaults for useLanIp services', () => { + const repo = createRepo({ + '.env.local': '', + [`${workerDir}/package.json`]: JSON.stringify( + { scripts: { dev: "wrangler dev --env 'dev'" } }, + null, + 2 + ), + [`${workerDir}/wrangler.jsonc`]: '{ "dev": { "port": 8794 } }', + [`${workerDir}/.dev.vars.example`]: [ + '# @url nextjs', + 'KILOCODE_BACKEND_BASE_URL=http://host.docker.internal:3000', + '# @url cloud-agent-next', + 'WORKER_URL=http://host.docker.internal:8794', + '# @url nextjs', + 'ALLOWED_ORIGINS=http://localhost:3000', + '', + ].join('\n'), + }); + try { + const plan = computePlan(repo.root, new Set(['cloud-agent-next'])); + assert.equal(plan.missingEnvLocal, false); + assert.equal(plan.devVarsChanges.length, 1); + const [change] = plan.devVarsChanges; + assert.ok(change); + assert.equal(change.isNew, true); + const content = change.newFileContent ?? ''; + assert.ok(content.includes('KILOCODE_BACKEND_BASE_URL=http://host.docker.internal:3000')); + assert.ok(content.includes('WORKER_URL=http://host.docker.internal:8794')); + // ORIGINS keys still use localhost even when example default has host.docker.internal + assert.ok(content.includes('ALLOWED_ORIGINS=http://localhost:3000')); + } finally { + repo.cleanup(); + } +}); diff --git a/services/cloud-agent-next/.dev.vars.example b/services/cloud-agent-next/.dev.vars.example index 96504553a..e4f2ded39 100644 --- a/services/cloud-agent-next/.dev.vars.example +++ b/services/cloud-agent-next/.dev.vars.example @@ -18,18 +18,18 @@ INTERNAL_API_SECRET=your-internal-api-secret-here #KILOCODE_ORG_ID_OVERRIDE=your-override-org-id-here # Kilocode backend base URL and KILO_OPENROUTER_BASE for API calls and session environment variables -# For local development, point to your local kilocode-backend -# Note: you wanna use your actual privatenet address here and not "localhost" -# pnpm run dev of kilocode-backend usually gives you both addrs on boot. +# For local development, point to your local kilocode-backend. +# Sandbox containers run under Docker Desktop, so `host.docker.internal` +# resolves to the host where Next.js is listening. # @url nextjs -KILOCODE_BACKEND_BASE_URL=http://192.168.x.x:3000 +KILOCODE_BACKEND_BASE_URL=http://host.docker.internal:3000 # @url nextjs/api -KILO_OPENROUTER_BASE=http://192.168.x.x:3000/api +KILO_OPENROUTER_BASE=http://host.docker.internal:3000/api # Worker base URL used by the wrapper to connect to /ingest. -# Use a host-reachable IP (not localhost) so sandbox containers can connect back. +# Sandbox containers reach the host via `host.docker.internal`. # @url cloud-agent-next -WORKER_URL=http://192.168.x.x:8794 +WORKER_URL=http://host.docker.internal:8794 # Timeout overrides (optional) CLI_TIMEOUT_SECONDS=900 @@ -69,9 +69,9 @@ R2_ATTACHMENTS_READONLY_ACCESS_KEY_ID="" R2_ATTACHMENTS_READONLY_SECRET_ACCESS_KEY="" # Session ingest URL used by the wrapper to post session data. -# Use a host-reachable IP (not localhost) so sandbox containers can connect back. +# Sandbox containers reach the host via `host.docker.internal`. # @url cloudflare-session-ingest -KILO_SESSION_INGEST_URL=http://192.168.x.x:8800 +KILO_SESSION_INGEST_URL=http://host.docker.internal:8800 # Local dev worker origins allowed to connect to /stream # @url nextjs,cloud-agent-next diff --git a/services/cloud-agent-next/wrangler.jsonc b/services/cloud-agent-next/wrangler.jsonc index d49cd8d1c..7c5446465 100644 --- a/services/cloud-agent-next/wrangler.jsonc +++ b/services/cloud-agent-next/wrangler.jsonc @@ -244,7 +244,7 @@ "STALE_THRESHOLD_MS": "600000", "PENDING_START_TIMEOUT_MS": "300000", "R2_ATTACHMENTS_BUCKET": "cloud-agent-attachments-dev", - "WS_ALLOWED_ORIGINS": "http://localhost:3000,http://192.168.200.174:3000", + "WS_ALLOWED_ORIGINS": "http://localhost:3000,http://host.docker.internal:3000", "KILO_SESSION_INGEST_URL": "http://localhost:8800", "PER_SESSION_SANDBOX_ORG_IDS": "*", },