From c88ef71736777fd86e6ce2cac7a7d8df3dc07fcb Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Tue, 31 Mar 2026 16:17:25 +0200 Subject: [PATCH 1/2] fix(kiloclaw): propagate controller_route_unavailable through DO RPC boundary The GatewayControllerError.code property was lost when crossing the DO RPC serialization boundary, causing startKiloCliRun to return a generic 500 instead of the controller_route_unavailable code the client needs to show the redeploy UI. Follow the established pattern: catch isErrorUnknownRoute in the DO and return null, then handle null in the platform route to emit the proper error code in the HTTP response body. --- .../kiloclaw-instance/kilo-cli-run.ts | 25 +-- kiloclaw/src/routes/platform.ts | 11 +- .../migrations/0064_add_kiloclaw_cli_runs.sql | 2 +- .../claw/components/StartKiloCliRunDialog.tsx | 158 +++++++++++++----- src/routers/kiloclaw-router.ts | 18 +- 5 files changed, 154 insertions(+), 60 deletions(-) diff --git a/kiloclaw/src/durable-objects/kiloclaw-instance/kilo-cli-run.ts b/kiloclaw/src/durable-objects/kiloclaw-instance/kilo-cli-run.ts index 85c5d343a7..43bba17bca 100644 --- a/kiloclaw/src/durable-objects/kiloclaw-instance/kilo-cli-run.ts +++ b/kiloclaw/src/durable-objects/kiloclaw-instance/kilo-cli-run.ts @@ -4,7 +4,7 @@ import { KiloCliRunStatusResponseSchema, GatewayCommandResponseSchema, } from '../gateway-controller-types'; -import { callGatewayController } from './gateway'; +import { callGatewayController, isErrorUnknownRoute } from './gateway'; import type { InstanceMutableState } from './types'; type KiloCliRunStartResponse = { @@ -29,19 +29,24 @@ export async function startKiloCliRun( state: InstanceMutableState, env: KiloClawEnv, prompt: string -): Promise { +): Promise { if (state.status !== 'running' || !state.flyMachineId) { throw Object.assign(new Error('Instance is not running'), { status: 409 }); } - return callGatewayController( - state, - env, - '/_kilo/cli-run/start', - 'POST', - KiloCliRunStartResponseSchema, - { prompt } - ); + try { + return await callGatewayController( + state, + env, + '/_kilo/cli-run/start', + 'POST', + KiloCliRunStartResponseSchema, + { prompt } + ); + } catch (error) { + if (isErrorUnknownRoute(error)) return null; + throw error; + } } /** diff --git a/kiloclaw/src/routes/platform.ts b/kiloclaw/src/routes/platform.ts index 9dd1a62f96..c699d53aa0 100644 --- a/kiloclaw/src/routes/platform.ts +++ b/kiloclaw/src/routes/platform.ts @@ -982,10 +982,17 @@ platform.post('/kilo-cli-run/start', async c => { stub => stub.startKiloCliRun(result.data.prompt), 'startKiloCliRun' ); + if (!response) { + return jsonError( + 'Kilo CLI agent not available (controller too old)', + 404, + 'controller_route_unavailable' + ); + } return c.json(response, 200); } catch (err) { - const { message, status } = sanitizeError(err, 'kilo-cli-run start'); - return jsonError(message, status); + const { message, status, code } = sanitizeOpenclawConfigError(err, 'kilo-cli-run start'); + return jsonError(message, status, code); } }); diff --git a/packages/db/src/migrations/0064_add_kiloclaw_cli_runs.sql b/packages/db/src/migrations/0064_add_kiloclaw_cli_runs.sql index cc18c6f420..5c1a216a94 100644 --- a/packages/db/src/migrations/0064_add_kiloclaw_cli_runs.sql +++ b/packages/db/src/migrations/0064_add_kiloclaw_cli_runs.sql @@ -11,4 +11,4 @@ CREATE TABLE "kiloclaw_cli_runs" ( --> statement-breakpoint ALTER TABLE "kiloclaw_cli_runs" ADD CONSTRAINT "kiloclaw_cli_runs_user_id_kilocode_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."kilocode_users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint CREATE INDEX "IDX_kiloclaw_cli_runs_user_id" ON "kiloclaw_cli_runs" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "IDX_kiloclaw_cli_runs_started_at" ON "kiloclaw_cli_runs" USING btree ("started_at"); \ No newline at end of file +CREATE INDEX "IDX_kiloclaw_cli_runs_started_at" ON "kiloclaw_cli_runs" USING btree ("started_at"); diff --git a/src/app/(app)/claw/components/StartKiloCliRunDialog.tsx b/src/app/(app)/claw/components/StartKiloCliRunDialog.tsx index c2bef8d98b..3887328a17 100644 --- a/src/app/(app)/claw/components/StartKiloCliRunDialog.tsx +++ b/src/app/(app)/claw/components/StartKiloCliRunDialog.tsx @@ -2,7 +2,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; -import { Loader2, Terminal } from 'lucide-react'; +import { AlertTriangle, Loader2, RotateCw, Terminal } from 'lucide-react'; +import { toast } from 'sonner'; import { Dialog, DialogContent, @@ -14,6 +15,19 @@ import { import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { useKiloClawMutations } from '@/hooks/useKiloClaw'; +import { AnimatedDots } from './AnimatedDots'; + +function isNeedsRedeployError(error: unknown): boolean { + return ( + typeof error === 'object' && + error !== null && + 'data' in error && + typeof (error as { data?: unknown }).data === 'object' && + (error as { data?: { upstreamCode?: unknown } }).data !== null && + (error as { data: { upstreamCode?: unknown } }).data.upstreamCode === + 'controller_route_unavailable' + ); +} export function StartKiloCliRunDialog({ open, @@ -26,6 +40,9 @@ export function StartKiloCliRunDialog({ const [prompt, setPrompt] = useState(''); const mutations = useKiloClawMutations(); const startMutation = mutations.startKiloCliRun; + const redeployMutation = mutations.restartMachine; + + const needsRedeploy = startMutation.isError && isNeedsRedeployError(startMutation.error); const handleStart = () => { const trimmed = prompt.trim(); @@ -41,6 +58,19 @@ export function StartKiloCliRunDialog({ ); }; + const handleRedeploy = () => { + redeployMutation.mutate( + { imageTag: 'latest' }, + { + onSuccess: () => { + toast.success('Upgrading to latest version'); + onOpenChange(false); + }, + onError: err => toast.error(err.message, { duration: 10000 }), + } + ); + }; + const handleOpenChange = (nextOpen: boolean) => { if (!nextOpen) { setPrompt(''); @@ -58,55 +88,91 @@ export function StartKiloCliRunDialog({ Recover with Kilo CLI Agent - If your KiloClaw instance is stuck or failing, the Kilo CLI agent can help diagnose and - fix the problem. Describe the issue below and the agent will work autonomously to - resolve it. + {needsRedeploy + ? 'Your instance needs to be redeployed before the recovery agent can run.' + : 'If your KiloClaw instance is stuck or failing, the Kilo CLI agent can help diagnose and fix the problem. Describe the issue below and the agent will work autonomously to resolve it.'} -
-