From 2ed92cd42adc01e86a81646d20b9c94cbccbe4e5 Mon Sep 17 00:00:00 2001 From: Alex Southwell <4596216+saphid@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:53:14 +1100 Subject: [PATCH] fix(kiro): slow kiro-cli starts no longer show Kiro as unavailable Co-Authored-By: Claude Opus 5.5 --- apps/server/src/provider/KiroProvider.test.ts | 45 +++++++++++++++++-- packages/provider-kiro/src/server/status.ts | 4 +- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/apps/server/src/provider/KiroProvider.test.ts b/apps/server/src/provider/KiroProvider.test.ts index b0b9143d97a55..1877c131e3158 100644 --- a/apps/server/src/provider/KiroProvider.test.ts +++ b/apps/server/src/provider/KiroProvider.test.ts @@ -3,13 +3,16 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import { KiroSettings } from "@t3tools/provider-kiro/settings"; import * as Deferred from "effect/Deferred"; +import type * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; +import * as TestClock from "effect/testing/TestClock"; import { checkKiroProviderStatus, @@ -23,18 +26,25 @@ const settings = Schema.decodeSync(KiroSettings)({ enabled: true }); interface CliOutput { readonly stdout: string; readonly code: number; + /** Holds stdout and the exit back this long, like a CLI slow to answer. */ + readonly delay?: Duration.Input; } function processHandle(output: CliOutput) { + const answered = Effect.sleep(output.delay ?? 0); return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(900_000_002), - exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(output.code)), + exitCode: Effect.as(answered, ChildProcessSpawner.ExitCode(output.code)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, - stdout: - output.stdout.length === 0 ? Stream.empty : Stream.succeed(encoder.encode(output.stdout)), + stdout: Stream.unwrap( + Effect.as( + answered, + output.stdout.length === 0 ? Stream.empty : Stream.succeed(encoder.encode(output.stdout)), + ), + ), stderr: Stream.empty, all: Stream.empty, getInputFd: () => Sink.drain, @@ -123,6 +133,35 @@ const check = ( }).pipe(Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))); describe("KiroProvider", () => { + it.effect.each([ + { + label: "answers after 6 s", + delay: "6 seconds", + status: "ready", + version: "2.27.0", + message: undefined, + }, + { + label: "never answers", + delay: "Infinity", + status: "error", + version: null, + message: "Failed to run `kiro-cli --version`.", + }, + ] as const)("gives `kiro-cli --version` 10 s when it $label", (row) => + Effect.gen(function* () { + const fiber = yield* check({ + "--version": { stdout: "kiro-cli 2.27.0\n", code: 0, delay: row.delay }, + "whoami --format json": { stdout: '{"accountType":"ApiKey","email":null}\n', code: 0 }, + }).pipe(Effect.forkChild); + yield* TestClock.adjust("10 seconds"); + const { snapshot } = yield* Fiber.join(fiber); + assert.equal(snapshot.status, row.status); + assert.equal(snapshot.version, row.version); + assert.equal(snapshot.message, row.message); + }), + ); + it.effect("reports a CLI whose version probe exits nonzero as failing to run", () => Effect.gen(function* () { const { snapshot, invoked } = yield* check({ diff --git a/packages/provider-kiro/src/server/status.ts b/packages/provider-kiro/src/server/status.ts index 39eed259b4be9..83de43df93a8e 100644 --- a/packages/provider-kiro/src/server/status.ts +++ b/packages/provider-kiro/src/server/status.ts @@ -26,7 +26,6 @@ import { AUTH_PROBE_TIMEOUT_MS, buildServerProvider, COMPACT_SLASH_COMMAND, - DEFAULT_TIMEOUT_MS, isCommandMissingCause, parseGenericCliVersion, buildSelectOptionDescriptor, @@ -54,6 +53,7 @@ const KIRO_BUILT_IN_MODELS: ReadonlyArray = [ }, ]; const KIRO_API_KEY_ENV = "KIRO_API_KEY"; +const VERSION_PROBE_TIMEOUT_MS = 10_000; /** * The agent picker is not a model trait, but the composer reads options per @@ -374,7 +374,7 @@ export const checkKiroProviderStatus = Effect.fn("checkKiroProviderStatus")(func } const versionResult = yield* runKiroCli(settings, ["--version"], environment).pipe( - Effect.timeoutOption(DEFAULT_TIMEOUT_MS), + Effect.timeoutOption(VERSION_PROBE_TIMEOUT_MS), Effect.result, ); if (Result.isFailure(versionResult) || Option.isNone(versionResult.success)) {