Skip to content

Commit 8a38b32

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/config-editor
# Conflicts: # src/app/(app)/claw/components/SettingsTab.tsx # src/hooks/useKiloClaw.ts # src/lib/kiloclaw/kiloclaw-internal-client.ts # src/lib/kiloclaw/types.ts
2 parents c013ac7 + c9e1ad7 commit 8a38b32

70 files changed

Lines changed: 3567 additions & 335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cloud-agent-next/src/session-service.test.ts

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ describe('SessionService', () => {
13031303
};
13041304
};
13051305

1306-
it.each([undefined, 'cloud-agent', 'app-builder'])(
1306+
it.each([undefined])(
13071307
'should NOT include question:deny for interactive platform %s',
13081308
async createdOnPlatform => {
13091309
const { sandbox, sandboxCreateSession } = setupForPlatformTest();
@@ -1332,34 +1332,40 @@ describe('SessionService', () => {
13321332
}
13331333
);
13341334

1335-
it.each(['slack', 'security-agent', 'webhook', 'code-review', 'auto-triage', 'autofix'])(
1336-
'should include question:deny for non-interactive platform %s',
1337-
async createdOnPlatform => {
1338-
const { sandbox, sandboxCreateSession } = setupForPlatformTest();
1339-
const sessionId: SessionId = 'agent_noninteractive_test';
1340-
mockedSetupWorkspace.mockResolvedValue({
1341-
workspacePath: `/workspace/org/user/sessions/${sessionId}`,
1342-
sessionHome: `/home/${sessionId}`,
1343-
});
1335+
it.each([
1336+
'cloud-agent',
1337+
'app-builder',
1338+
'slack',
1339+
'security-agent',
1340+
'webhook',
1341+
'code-review',
1342+
'auto-triage',
1343+
'autofix',
1344+
])('should include question:deny for non-interactive platform %s', async createdOnPlatform => {
1345+
const { sandbox, sandboxCreateSession } = setupForPlatformTest();
1346+
const sessionId: SessionId = 'agent_noninteractive_test';
1347+
mockedSetupWorkspace.mockResolvedValue({
1348+
workspacePath: `/workspace/org/user/sessions/${sessionId}`,
1349+
sessionHome: `/home/${sessionId}`,
1350+
});
13441351

1345-
const service = new SessionService();
1346-
await service.initiate({
1347-
sandbox,
1348-
sandboxId: 'org__user',
1349-
orgId: 'org',
1350-
userId: 'user',
1351-
sessionId,
1352-
kilocodeToken: 'token',
1353-
kilocodeModel: 'test-model',
1354-
githubRepo: 'acme/repo',
1355-
env: mockEnv,
1356-
createdOnPlatform,
1357-
});
1352+
const service = new SessionService();
1353+
await service.initiate({
1354+
sandbox,
1355+
sandboxId: 'org__user',
1356+
orgId: 'org',
1357+
userId: 'user',
1358+
sessionId,
1359+
kilocodeToken: 'token',
1360+
kilocodeModel: 'test-model',
1361+
githubRepo: 'acme/repo',
1362+
env: mockEnv,
1363+
createdOnPlatform,
1364+
});
13581365

1359-
const config = getConfigContent(sandboxCreateSession);
1360-
expect(config.permission.question).toBe('deny');
1361-
}
1362-
);
1366+
const config = getConfigContent(sandboxCreateSession);
1367+
expect(config.permission.question).toBe('deny');
1368+
});
13631369

13641370
it('should include read-only command guard policy for code-review sessions', async () => {
13651371
const { sandbox, sandboxCreateSession } = setupForPlatformTest();

cloud-agent-next/src/session-service.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,10 +578,7 @@ export class SessionService {
578578
if (env.KILO_OPENROUTER_BASE) {
579579
providerOptions.baseURL = env.KILO_OPENROUTER_BASE;
580580
}
581-
const isInteractive =
582-
!createdOnPlatform ||
583-
createdOnPlatform === 'cloud-agent' ||
584-
createdOnPlatform === 'app-builder';
581+
const isInteractive = !createdOnPlatform;
585582
const commandGuardPolicy = getCommandGuardPolicy(createdOnPlatform);
586583

587584
const permission: Record<string, unknown> = {

cloud-agent-next/wrangler.jsonc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
"image": "./Dockerfile",
126126
"instance_type": "standard-4",
127127
"image_vars": {
128-
"KILOCODE_CLI_VERSION": "7.0.37",
128+
"KILOCODE_CLI_VERSION": "7.0.46",
129129
},
130130
"max_instances": 150,
131131
"rollout_active_grace_period": 120,
@@ -257,7 +257,7 @@
257257
"image": "./Dockerfile.dev",
258258
"instance_type": "standard-4",
259259
"image_vars": {
260-
"KILOCODE_CLI_VERSION": "7.0.37",
260+
"KILOCODE_CLI_VERSION": "7.0.46",
261261
},
262262
"max_instances": 10,
263263
"rollout_active_grace_period": 60,

cloudflare-app-builder/start-dev.sh

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# - cloudflare-git-token-service (port 8795)
1212
# - cloudflare-app-builder (port 8790)
1313
# - cloudflare-images-mcp (port 8796)
14+
# - cloudflare-webhook-agent-ingest (port 8793)
1415
# - ngrok (forwarding to port 8790)
1516
#
1617
# Requirements:
@@ -79,17 +80,18 @@ fi
7980
# Create new tmux session with first window for db-proxy
8081
tmux new-session -d -s "$SESSION_NAME" -n "services" -c "$PROJECT_ROOT"
8182

82-
# Split into grid for 8 services
83+
# Split into grid for 9 services + ngrok
8384
# First split horizontally (top/bottom)
8485
tmux split-window -v -t "$SESSION_NAME:services" -c "$PROJECT_ROOT"
85-
# Split top pane vertically into 4
86+
# Split top pane vertically into 5
87+
tmux split-window -h -t "$SESSION_NAME:services.0" -c "$PROJECT_ROOT"
8688
tmux split-window -h -t "$SESSION_NAME:services.0" -c "$PROJECT_ROOT"
8789
tmux split-window -h -t "$SESSION_NAME:services.0" -c "$PROJECT_ROOT"
8890
tmux split-window -h -t "$SESSION_NAME:services.0" -c "$PROJECT_ROOT"
8991
# Split bottom pane vertically into 4
90-
tmux split-window -h -t "$SESSION_NAME:services.4" -c "$PROJECT_ROOT"
91-
tmux split-window -h -t "$SESSION_NAME:services.4" -c "$PROJECT_ROOT"
92-
tmux split-window -h -t "$SESSION_NAME:services.4" -c "$PROJECT_ROOT"
92+
tmux split-window -h -t "$SESSION_NAME:services.5" -c "$PROJECT_ROOT"
93+
tmux split-window -h -t "$SESSION_NAME:services.5" -c "$PROJECT_ROOT"
94+
tmux split-window -h -t "$SESSION_NAME:services.5" -c "$PROJECT_ROOT"
9395

9496
# Arrange panes in a tiled layout
9597
tmux select-layout -t "$SESSION_NAME:services" tiled
@@ -112,28 +114,32 @@ tmux send-keys -t "$SESSION_NAME:services.1" "cd $PROJECT_ROOT/cloudflare-sessio
112114
tmux select-pane -t "$SESSION_NAME:services.2" -T "cloud-agent (8788)"
113115
tmux send-keys -t "$SESSION_NAME:services.2" "cd $PROJECT_ROOT/cloud-agent && echo '🤖 Starting cloud-agent (port 8788)...' && pnpm exec wrangler dev --inspector-port 9231" C-m
114116

115-
# Pane 3 (top-right): cloudflare-images-mcp
117+
# Pane 3: cloudflare-images-mcp
116118
tmux select-pane -t "$SESSION_NAME:services.3" -T "images-mcp (8796)"
117119
tmux send-keys -t "$SESSION_NAME:services.3" "cd $PROJECT_ROOT/cloudflare-images-mcp && echo '🖼️ Starting cloudflare-images-mcp (port 8796)...' && pnpm exec wrangler dev --env dev --inspector-port 9236" C-m
118120

119-
# Pane 4 (bottom-left): cloudflare-git-token-service
120-
tmux select-pane -t "$SESSION_NAME:services.4" -T "git-token-service (8795)"
121-
tmux send-keys -t "$SESSION_NAME:services.4" "cd $PROJECT_ROOT/cloudflare-git-token-service && echo '🔑 Starting cloudflare-git-token-service (port 8795)...' && pnpm exec wrangler dev --inspector-port 9235" C-m
121+
# Pane 4: cloudflare-webhook-agent-ingest
122+
tmux select-pane -t "$SESSION_NAME:services.4" -T "webhook-ingest (8793)"
123+
tmux send-keys -t "$SESSION_NAME:services.4" "cd $PROJECT_ROOT/cloudflare-webhook-agent-ingest && echo '🪝 Starting cloudflare-webhook-agent-ingest (port 8793)...' && pnpm exec wrangler dev --env dev --inspector-port 9237" C-m
124+
125+
# Pane 5: cloudflare-git-token-service
126+
tmux select-pane -t "$SESSION_NAME:services.5" -T "git-token-service (8795)"
127+
tmux send-keys -t "$SESSION_NAME:services.5" "cd $PROJECT_ROOT/cloudflare-git-token-service && echo '🔑 Starting cloudflare-git-token-service (port 8795)...' && pnpm exec wrangler dev --inspector-port 9235" C-m
122128

123-
# Pane 5: cloudflare-app-builder
124-
tmux select-pane -t "$SESSION_NAME:services.5" -T "app-builder (8790)"
125-
tmux send-keys -t "$SESSION_NAME:services.5" "cd $PROJECT_ROOT/cloudflare-app-builder && echo '🏗️ Starting cloudflare-app-builder (port 8790)...' && pnpm exec wrangler dev --inspector-port 9232" C-m
129+
# Pane 6: cloudflare-app-builder
130+
tmux select-pane -t "$SESSION_NAME:services.6" -T "app-builder (8790)"
131+
tmux send-keys -t "$SESSION_NAME:services.6" "cd $PROJECT_ROOT/cloudflare-app-builder && echo '🏗️ Starting cloudflare-app-builder (port 8790)...' && pnpm exec wrangler dev --inspector-port 9232" C-m
126132

127-
# Pane 6: ngrok
128-
tmux select-pane -t "$SESSION_NAME:services.6" -T "ngrok → 8790"
129-
tmux send-keys -t "$SESSION_NAME:services.6" "echo '🌐 Starting ngrok (forwarding to port 8790)...' && ngrok http 8790" C-m
133+
# Pane 7: ngrok
134+
tmux select-pane -t "$SESSION_NAME:services.7" -T "ngrok → 8790"
135+
tmux send-keys -t "$SESSION_NAME:services.7" "echo '🌐 Starting ngrok (forwarding to port 8790)...' && ngrok http 8790" C-m
130136

131-
# Pane 7 (bottom-right): cloud-agent-next
132-
tmux select-pane -t "$SESSION_NAME:services.7" -T "cloud-agent-next (8794)"
133-
tmux send-keys -t "$SESSION_NAME:services.7" "cd $PROJECT_ROOT/cloud-agent-next && echo '☁️ Starting cloud-agent-next (port 8794)...' && pnpm run dev" C-m
137+
# Pane 8: cloud-agent-next
138+
tmux select-pane -t "$SESSION_NAME:services.8" -T "cloud-agent-next (8794)"
139+
tmux send-keys -t "$SESSION_NAME:services.8" "cd $PROJECT_ROOT/cloud-agent-next && echo '☁️ Starting cloud-agent-next (port 8794)...' && pnpm run dev" C-m
134140

135141
# Select the ngrok pane by default
136-
tmux select-pane -t "$SESSION_NAME:services.6"
142+
tmux select-pane -t "$SESSION_NAME:services.7"
137143

138144
echo ""
139145
echo "╔══════════════════════════════════════════════════════════════════╗"
@@ -147,6 +153,7 @@ echo "║ • cloud-agent-next → http://localhost:8794
147153
echo "║ • git-token-service → http://localhost:8795 ║"
148154
echo "║ • cloudflare-app-builder → http://localhost:8790 ║"
149155
echo "║ • cloudflare-images-mcp → http://localhost:8796 ║"
156+
echo "║ • webhook-agent-ingest → http://localhost:8793 ║"
150157
echo "║ • ngrok → forwarding to :8790 ║"
151158
echo "╠══════════════════════════════════════════════════════════════════╣"
152159
echo "║ tmux Navigation: ║"

cloudflare-webhook-agent-ingest/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ A webhook trigger service for Kilo Code that allows users to create named endpoi
66

77
- Create named webhook endpoints per user/org
88
- Capture and store last 50 requests per ingest endpoint
9-
- Queue-based delivery to cloud-agent
9+
- Queue-based delivery to cloud-agent-next
1010
- Internal API key authentication for CRUD routes (backend-to-backend)
1111

1212
## Architecture
1313

1414
- **Durable Objects**: TriggerDO for per-trigger request storage with SQLite
15-
- **Queue**: WEBHOOK_DELIVERY_QUEUE for reliable delivery to cloud-agent
16-
- **Service Binding**: Direct connection to cloud-agent worker
15+
- **Queue**: WEBHOOK_DELIVERY_QUEUE for reliable delivery to cloud-agent-next
16+
- **Service Binding**: Direct connection to cloud-agent-next worker
1717

1818
> **Note**: KV registry for abuse prevention is planned for future implementation.
1919
> See [plans/webhook-catcher-design.md](plans/webhook-catcher-design.md#future-work-kv-registry-for-abuse-prevention).
@@ -218,12 +218,12 @@ curl -X DELETE "https://localhost:8793/api/triggers/org/org_xyz789/my-github-web
218218

219219
Captured requests go through the following status lifecycle:
220220

221-
| Status | Description |
222-
| ------------ | ------------------------------ |
223-
| `captured` | Request received and stored |
224-
| `inprogress` | Being processed by cloud-agent |
225-
| `success` | Successfully processed |
226-
| `failed` | Processing failed |
221+
| Status | Description |
222+
| ------------ | ----------------------------------- |
223+
| `captured` | Request received and stored |
224+
| `inprogress` | Being processed by cloud-agent-next |
225+
| `success` | Successfully processed |
226+
| `failed` | Processing failed |
227227

228228
## Environment Variables
229229

@@ -266,10 +266,10 @@ The following resources are already configured:
266266

267267
### Service Bindings
268268

269-
| Binding | Target Worker | Environment |
270-
| ------------- | ----------------- | ----------- |
271-
| `CLOUD_AGENT` | `cloud-agent` | Production |
272-
| `CLOUD_AGENT` | `cloud-agent-dev` | Development |
269+
| Binding | Target Worker | Environment |
270+
| ------------- | ---------------------- | ----------- |
271+
| `CLOUD_AGENT` | `cloud-agent-next` | Production |
272+
| `CLOUD_AGENT` | `cloud-agent-next-dev` | Development |
273273

274274
### KV Namespaces
275275

@@ -326,8 +326,8 @@ The worker includes a queue consumer that processes webhook delivery messages fr
326326
2. Checks idempotency (only processes requests in `captured` status)
327327
3. Gets or mints an API token (cached in KV for 30 minutes)
328328
4. Renders the prompt from the trigger's template
329-
5. Calls cloud-agent's `prepareSession` to create a session
330-
6. Calls cloud-agent's `initiateFromKilocodeSessionV2` to start processing
329+
5. Calls cloud-agent-next's `prepareSession` to create a session
330+
6. Calls cloud-agent-next's `initiateFromKilocodeSessionV2` to start processing
331331
7. Updates request status through the lifecycle
332332

333333
### Queue Configuration
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { classifyInitiateResponse } from './initiate-response';
3+
4+
function makeResponse(status: number, body = ''): Response {
5+
return new Response(body, { status });
6+
}
7+
8+
describe('classifyInitiateResponse', () => {
9+
it('returns ack for 200 OK', async () => {
10+
const result = await classifyInitiateResponse(makeResponse(200, '{}'));
11+
expect(result).toEqual({ action: 'ack' });
12+
});
13+
14+
it('returns ack for 409 (execution already in progress)', async () => {
15+
const result = await classifyInitiateResponse(
16+
makeResponse(409, 'Execution already in progress')
17+
);
18+
expect(result).toEqual({ action: 'ack' });
19+
});
20+
21+
it('returns ack for 409 with empty body', async () => {
22+
const result = await classifyInitiateResponse(makeResponse(409));
23+
expect(result).toEqual({ action: 'ack' });
24+
});
25+
26+
it('returns ack for 400 "Session has already been initiated"', async () => {
27+
const result = await classifyInitiateResponse(
28+
makeResponse(400, 'Session has already been initiated')
29+
);
30+
expect(result).toEqual({ action: 'ack' });
31+
});
32+
33+
it('returns retry for 503 (retryable sandbox failure)', async () => {
34+
const result = await classifyInitiateResponse(makeResponse(503, 'sandbox startup failed'));
35+
expect(result).toEqual({
36+
action: 'retry',
37+
errorMessage: 'initiateFromKilocodeSessionV2 returned retryable 503: sandbox startup failed',
38+
});
39+
});
40+
41+
it('returns retry for 503 with empty body', async () => {
42+
const result = await classifyInitiateResponse(makeResponse(503));
43+
expect(result).toEqual({
44+
action: 'retry',
45+
errorMessage: 'initiateFromKilocodeSessionV2 returned retryable 503: ',
46+
});
47+
});
48+
49+
it('returns throw for generic 500', async () => {
50+
const result = await classifyInitiateResponse(makeResponse(500, 'Internal server error'));
51+
expect(result).toEqual({
52+
action: 'throw',
53+
errorMessage: 'initiateFromKilocodeSessionV2 failed: 500 - Internal server error',
54+
});
55+
});
56+
57+
it('returns throw for 502', async () => {
58+
const result = await classifyInitiateResponse(makeResponse(502, 'Bad gateway'));
59+
expect(result).toEqual({
60+
action: 'throw',
61+
errorMessage: 'initiateFromKilocodeSessionV2 failed: 502 - Bad gateway',
62+
});
63+
});
64+
65+
it('returns fail for 402 (insufficient balance)', async () => {
66+
const result = await classifyInitiateResponse(makeResponse(402, 'Insufficient credits'));
67+
expect(result).toEqual({
68+
action: 'fail',
69+
errorMessage: 'Insufficient credits',
70+
});
71+
});
72+
73+
it('returns fail for 402 with empty body, using default message', async () => {
74+
const result = await classifyInitiateResponse(makeResponse(402));
75+
expect(result).toEqual({
76+
action: 'fail',
77+
errorMessage: 'Insufficient balance',
78+
});
79+
});
80+
81+
it('returns fail for other 4xx errors', async () => {
82+
const result = await classifyInitiateResponse(makeResponse(422, 'Validation failed'));
83+
expect(result).toEqual({
84+
action: 'fail',
85+
errorMessage: 'Validation failed',
86+
});
87+
});
88+
89+
it('returns fail for 400 without "Session has already been initiated"', async () => {
90+
const result = await classifyInitiateResponse(makeResponse(400, 'Bad request'));
91+
expect(result).toEqual({
92+
action: 'fail',
93+
errorMessage: 'Bad request',
94+
});
95+
});
96+
97+
// 503 is classified distinctly from other 5xx — verify ordering
98+
it('classifies 503 as retry, not generic throw', async () => {
99+
const result = await classifyInitiateResponse(makeResponse(503, 'workspace error'));
100+
expect(result.action).toBe('retry');
101+
});
102+
103+
it('classifies 500 as throw, not retry', async () => {
104+
const result = await classifyInitiateResponse(makeResponse(500, 'crash'));
105+
expect(result.action).toBe('throw');
106+
});
107+
});

0 commit comments

Comments
 (0)