Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 450 additions & 0 deletions .github/workflows/deploy-cloud-agent-next-staging.yml

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions services/cloud-agent-next/src/callbacks/queue-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { describe, expect, it } from 'vitest';
import { CALLBACK_DELIVERY_MAX_ATTEMPTS } from './delivery.js';

type QueueConsumer = { queue?: string; max_retries?: number };
type EnvironmentConfig = { queues?: { consumers?: QueueConsumer[] } };
type WranglerConfig = {
queues?: { consumers?: QueueConsumer[] };
env?: { dev?: { queues?: { consumers?: QueueConsumer[] } } };
env?: { dev?: EnvironmentConfig; staging?: EnvironmentConfig };
};

const CONFIGURED_REDELIVERIES = CALLBACK_DELIVERY_MAX_ATTEMPTS - 1;
Expand All @@ -18,16 +19,20 @@ function readWranglerConfig(): WranglerConfig {
}

describe('callback queue retry configuration', () => {
it('allows the application callback retry budget in default and dev consumers', () => {
it('allows the application callback retry budget in every environment', () => {
const config = readWranglerConfig();
const production = config.queues?.consumers?.find(
consumer => consumer.queue === 'cloud-agent-next-callback-queue'
);
const dev = config.env?.dev?.queues?.consumers?.find(
consumer => consumer.queue === 'cloud-agent-next-callback-queue-dev'
);
const staging = config.env?.staging?.queues?.consumers?.find(
consumer => consumer.queue === 'cloud-agent-next-callback-queue-staging'
);

expect(production?.max_retries).toBe(CONFIGURED_REDELIVERIES);
expect(dev?.max_retries).toBe(CONFIGURED_REDELIVERIES);
expect(staging?.max_retries).toBe(CONFIGURED_REDELIVERIES);
});
});
28 changes: 22 additions & 6 deletions services/cloud-agent-next/src/kilo/devcontainer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parse } from 'jsonc-parser';
import { describe, expect, it, vi } from 'vitest';
import {
bringUpDevContainer,
Expand Down Expand Up @@ -77,17 +78,32 @@ describe('sandbox image versions', () => {
fileURLToPath(new URL('../../Dockerfile.dind', import.meta.url).href),
'utf8'
);
const wranglerConfig = readFileSync(
fileURLToPath(new URL('../../wrangler.jsonc', import.meta.url).href),
'utf8'
);
const imageVar = `"KILOCODE_CLI_VERSION": "${KILO_CLI_VERSION}"`;
const wranglerConfig = parse(
readFileSync(fileURLToPath(new URL('../../wrangler.jsonc', import.meta.url).href), 'utf8')
) as {
containers?: Array<{ image_vars?: { KILOCODE_CLI_VERSION?: string } }>;
env?: {
dev?: { containers?: Array<{ image_vars?: { KILOCODE_CLI_VERSION?: string } }> };
staging?: { containers?: Array<{ image_vars?: { KILOCODE_CLI_VERSION?: string } }> };
};
};

expect(wrapperPackageJson.dependencies['@kilocode/sdk']).toBe(KILO_CLI_VERSION);
expect(dockerfile).toContain(`ARG KILOCODE_CLI_VERSION="${KILO_CLI_VERSION}"`);
expect(devDockerfile).toContain(`ARG KILOCODE_CLI_VERSION="${KILO_CLI_VERSION}"`);
expect(dindDockerfile).toContain(`ARG KILOCODE_CLI_VERSION="${KILO_CLI_VERSION}"`);
expect(wranglerConfig.split(imageVar)).toHaveLength(7);
for (const containers of [
wranglerConfig.containers,
wranglerConfig.env?.dev?.containers,
wranglerConfig.env?.staging?.containers,
]) {
expect(containers).toHaveLength(3);
expect(containers?.map(container => container.image_vars?.KILOCODE_CLI_VERSION)).toEqual([
KILO_CLI_VERSION,
KILO_CLI_VERSION,
KILO_CLI_VERSION,
]);
}
expect(DEFAULT_SLASH_COMMANDS_SOURCE).toBe(`kilo@${KILO_CLI_VERSION}`);
});
});
Expand Down
4 changes: 2 additions & 2 deletions services/cloud-agent-next/src/persistence/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export type PersistenceEnv = {
/** Worker base URL for building wrapper ingest WebSocket endpoints */
WORKER_URL?: string;

/** Shared secret for internal service-to-service authentication */
INTERNAL_API_SECRET_PROD: SecretsStoreSecret;
/** Optional production Secrets Store binding; staging uses the direct Worker secret. */
INTERNAL_API_SECRET_PROD?: SecretsStoreSecret;

R2_ENDPOINT?: string;
R2_ATTACHMENTS_READONLY_ACCESS_KEY_ID?: string;
Expand Down
13 changes: 13 additions & 0 deletions services/cloud-agent-next/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ vi.mock('./telemetry/report-consumer.js', () => ({
CLOUD_AGENT_REPORT_QUEUE_NAMES: new Set([
'cloud-agent-next-report-queue',
'cloud-agent-next-report-queue-dev',
'cloud-agent-next-report-queue-staging',
'cloud-agent-next-report-queue-test',
]),
consumeCloudAgentReportBatch: consumeCloudAgentReportBatchMock,
Expand Down Expand Up @@ -198,6 +199,18 @@ describe('server background reporting', () => {
expect(consumeCloudAgentReportBatchMock).toHaveBeenCalledWith(batch, env);
});

it('routes isolated staging report queue batches to the Cloud Agent report consumer', async () => {
const env = createEnv();
const batch = {
queue: 'cloud-agent-next-report-queue-staging',
messages: [],
} as unknown as MessageBatch<unknown>;

await worker.queue(batch, env as unknown as Env);

expect(consumeCloudAgentReportBatchMock).toHaveBeenCalledWith(batch, env);
});

it('runs reporting retention cleanup from the scheduled handler', async () => {
const env = createEnv();

Expand Down
32 changes: 32 additions & 0 deletions services/cloud-agent-next/src/staging-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parse } from 'jsonc-parser';
import { describe, expect, it } from 'vitest';

type WranglerConfig = {
env?: {
staging?: {
triggers?: { crons?: string[] };
secrets_store_secrets?: unknown[];
};
};
};

function readWranglerConfig(): WranglerConfig {
const configPath = fileURLToPath(new URL('../wrangler.jsonc', import.meta.url).href);
return parse(readFileSync(configPath, 'utf8')) as WranglerConfig;
}

describe('staging deployment configuration', () => {
it('does not schedule production report retention cleanup', () => {
const config = readWranglerConfig();

expect(config.env?.staging?.triggers?.crons ?? []).toEqual([]);
});

it('uses direct Worker secrets instead of Secrets Store bindings', () => {
const config = readWranglerConfig();

expect(config.env?.staging?.secrets_store_secrets).toBeUndefined();
});
});
1 change: 1 addition & 0 deletions services/cloud-agent-next/src/telemetry/report-consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createCloudAgentReportStore } from './report-store.js';
export const CLOUD_AGENT_REPORT_QUEUE_NAMES = new Set([
'cloud-agent-next-report-queue',
'cloud-agent-next-report-queue-dev',
'cloud-agent-next-report-queue-staging',
'cloud-agent-next-report-queue-test',
]);

Expand Down
4 changes: 2 additions & 2 deletions services/cloud-agent-next/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ export type Env = {
USER_KILO_FACADE: DurableObjectNamespace<UserKiloFacade>;
/** Service binding for the session ingest worker */
SESSION_INGEST: SessionIngestBinding;
/** Shared secret for internal service-to-service authentication */
INTERNAL_API_SECRET_PROD: SecretsStoreSecret;
/** Optional production Secrets Store binding; staging uses the direct Worker secret. */
INTERNAL_API_SECRET_PROD?: SecretsStoreSecret;
/** R2 bucket for storing session logs */
R2_BUCKET: R2Bucket;
/** Queue for callback messages (optional - supports incremental rollout) */
Expand Down
48 changes: 45 additions & 3 deletions services/cloud-agent-next/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: 0264ea9ef0ff7fd80bb9ebde4d3d6652)
// Generated by Wrangler by running `wrangler types` (hash: 58d62ff0a69f81d997b2a46b6819e5c8)
// Runtime types generated with workerd@1.20260508.1 2025-09-15 nodejs_compat
declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./src/index");
durableNamespaces: "Sandbox" | "CloudAgentSession" | "SandboxSmall" | "SandboxDIND";
durableNamespaces: "Sandbox" | "CloudAgentSession" | "SandboxSmall" | "SandboxDIND" | "UserKiloFacade";
}
interface DevEnv {
R2_BUCKET: R2Bucket;
Expand Down Expand Up @@ -42,16 +42,57 @@ declare namespace Cloudflare {
SandboxSmall: DurableObjectNamespace<import("./src/index").SandboxSmall>;
SandboxDIND: DurableObjectNamespace<import("./src/index").SandboxDIND>;
CLOUD_AGENT_SESSION: DurableObjectNamespace<import("./src/index").CloudAgentSession>;
USER_KILO_FACADE: DurableObjectNamespace<import("./src/index").UserKiloFacade>;
SESSION_INGEST: Service /* entrypoint SessionIngestRPC from session-ingest */;
GIT_TOKEN_SERVICE: Service /* entrypoint GitTokenRPCEntrypoint from git-token-service-dev */;
NOTIFICATIONS: Service /* entrypoint NotificationsService from notifications */;
}
interface StagingEnv {
R2_BUCKET: R2Bucket;
HYPERDRIVE: Hyperdrive;
CALLBACK_QUEUE: Queue;
CLOUD_AGENT_REPORT_QUEUE: Queue;
GITHUB_LITE_APP_SLUG: "kiloconnect-lite";
GITHUB_LITE_APP_BOT_USER_ID: "257753004";
R2_ATTACHMENTS_BUCKET: "cloud-agent-attachments";
PER_SESSION_SANDBOX_ORG_IDS: "*";
NEXTAUTH_SECRET: string;
KILO_SESSION_INGEST_URL: string;
WORKER_URL: string;
WS_ALLOWED_ORIGINS: string;
KILOCODE_BACKEND_BASE_URL: string;
KILO_OPENROUTER_BASE: string;
INTERNAL_API_SECRET: string;
R2_ENDPOINT: string;
R2_ATTACHMENTS_READONLY_ACCESS_KEY_ID: string;
R2_ATTACHMENTS_READONLY_SECRET_ACCESS_KEY: string;
AGENT_ENV_VARS_PRIVATE_KEY: string;
GITHUB_APP_ID: string;
GITHUB_APP_PRIVATE_KEY: string;
CLI_TIMEOUT_SECONDS: string;
REAPER_INTERVAL_MS: string;
STALE_THRESHOLD_MS: string;
PENDING_START_TIMEOUT_MS: string;
GITHUB_APP_SLUG: string;
GITHUB_APP_BOT_USER_ID: string;
GITHUB_LITE_APP_ID: string;
GITHUB_LITE_APP_PRIVATE_KEY: string;
KILOCODE_SANDBOX_BACKEND_BASE_URL: string;
Sandbox: DurableObjectNamespace<import("./src/index").Sandbox>;
SandboxSmall: DurableObjectNamespace<import("./src/index").SandboxSmall>;
SandboxDIND: DurableObjectNamespace<import("./src/index").SandboxDIND>;
CLOUD_AGENT_SESSION: DurableObjectNamespace<import("./src/index").CloudAgentSession>;
USER_KILO_FACADE: DurableObjectNamespace<import("./src/index").UserKiloFacade>;
SESSION_INGEST: Service /* entrypoint SessionIngestRPC from session-ingest */;
GIT_TOKEN_SERVICE: Service /* entrypoint GitTokenRPCEntrypoint from git-token-service */;
NOTIFICATIONS: Service /* entrypoint NotificationsService from notifications */;
}
interface Env {
R2_BUCKET: R2Bucket;
HYPERDRIVE: Hyperdrive;
CALLBACK_QUEUE: Queue;
CLOUD_AGENT_REPORT_QUEUE: Queue;
INTERNAL_API_SECRET_PROD: SecretsStoreSecret;
INTERNAL_API_SECRET_PROD?: SecretsStoreSecret;
GITHUB_LITE_APP_SLUG: "" | "kiloconnect-lite";
GITHUB_LITE_APP_BOT_USER_ID: "" | "257753004";
R2_ATTACHMENTS_BUCKET: "cloud-agent-attachments-dev" | "cloud-agent-attachments";
Expand Down Expand Up @@ -82,6 +123,7 @@ declare namespace Cloudflare {
SandboxSmall: DurableObjectNamespace<import("./src/index").SandboxSmall>;
SandboxDIND: DurableObjectNamespace<import("./src/index").SandboxDIND>;
CLOUD_AGENT_SESSION: DurableObjectNamespace<import("./src/index").CloudAgentSession>;
USER_KILO_FACADE: DurableObjectNamespace<import("./src/index").UserKiloFacade>;
SESSION_INGEST: Service /* entrypoint SessionIngestRPC from session-ingest */;
GIT_TOKEN_SERVICE: Service /* entrypoint GitTokenRPCEntrypoint from git-token-service-dev */ | Service /* entrypoint GitTokenRPCEntrypoint from git-token-service */;
NOTIFICATIONS: Service /* entrypoint NotificationsService from notifications */;
Expand Down
140 changes: 140 additions & 0 deletions services/cloud-agent-next/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -403,5 +403,145 @@
],
},
},
"staging": {
"name": "cloud-agent-next-staging",
"workers_dev": false,
"preview_urls": false,
"triggers": {
"crons": [],
},
"routes": [
{
"pattern": "cloud-agent-next-staging.kilosessions.ai",
"custom_domain": true,
},
],
"vars": {
"KILOCODE_BACKEND_BASE_URL": "https://api.kilo.ai",
"KILO_OPENROUTER_BASE": "https://api.kilo.ai/api",
"GITHUB_APP_SLUG": "kiloconnect",
"GITHUB_APP_BOT_USER_ID": "240665456",
"GITHUB_LITE_APP_SLUG": "kiloconnect-lite",
"GITHUB_LITE_APP_BOT_USER_ID": "257753004",
"WORKER_URL": "https://cloud-agent-next-staging.kilosessions.ai",
"CLI_TIMEOUT_SECONDS": "900",
"REAPER_INTERVAL_MS": "300000",
"R2_ATTACHMENTS_BUCKET": "cloud-agent-attachments",
"R2_ENDPOINT": "https://e115e769bcdd4c3d66af59d3332cb394.r2.cloudflarestorage.com",
"WS_ALLOWED_ORIGINS": "https://app.kilo.ai,https://api.kilo.ai,https://cloud-agent-next-staging.kilosessions.ai",
"KILO_SESSION_INGEST_URL": "https://ingest.kilosessions.ai",
"PER_SESSION_SANDBOX_ORG_IDS": "*",
},
"placement": { "mode": "smart" },
"services": [
{
"binding": "SESSION_INGEST",
"service": "session-ingest",
"entrypoint": "SessionIngestRPC",
},
{
"binding": "GIT_TOKEN_SERVICE",
"service": "git-token-service",
"entrypoint": "GitTokenRPCEntrypoint",
},
{
"binding": "NOTIFICATIONS",
"service": "notifications",
"entrypoint": "NotificationsService",
},
],
"r2_buckets": [
{
"binding": "R2_BUCKET",
"bucket_name": "kilocode-sessions-staging",
},
],
"containers": [
{
"class_name": "Sandbox",
"image": "./Dockerfile",
"instance_type": "standard-4",
"image_vars": {
"KILOCODE_CLI_VERSION": "7.3.21",
},
"max_instances": 10,
"rollout_active_grace_period": 60,
},
{
"class_name": "SandboxSmall",
"image": "./Dockerfile",
"instance_type": "standard-3",
"image_vars": {
"KILOCODE_CLI_VERSION": "7.3.21",
},
"max_instances": 2,
"rollout_active_grace_period": 60,
},
{
"class_name": "SandboxDIND",
"image": "./Dockerfile.dind",
"instance_type": "standard-3",
"image_vars": {
"KILOCODE_CLI_VERSION": "7.3.21",
},
"max_instances": 2,
"rollout_active_grace_period": 60,
},
],
"durable_objects": {
"bindings": [
{
"class_name": "Sandbox",
"name": "Sandbox",
},
{
"class_name": "SandboxSmall",
"name": "SandboxSmall",
},
{
"class_name": "SandboxDIND",
"name": "SandboxDIND",
},
{
"class_name": "CloudAgentSession",
"name": "CLOUD_AGENT_SESSION",
},
{
"class_name": "UserKiloFacade",
"name": "USER_KILO_FACADE",
},
],
},
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "624ec80650dd414199349f4e217ddb10",
},
],
"queues": {
"producers": [
{
"binding": "CALLBACK_QUEUE",
"queue": "cloud-agent-next-callback-queue-staging",
},
{
"binding": "CLOUD_AGENT_REPORT_QUEUE",
"queue": "cloud-agent-next-report-queue-staging",
},
],
"consumers": [
{
"queue": "cloud-agent-next-callback-queue-staging",
"max_batch_size": 5,
"max_retries": 4,
},
{
"queue": "cloud-agent-next-report-queue-staging",
"max_retries": 3,
"dead_letter_queue": "cloud-agent-next-report-queue-dlq-staging",
},
],
},
},
},
}