1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { ProviderInstanceId } from "@t3tools/contracts";
4import { KiroSettings } from "@t3tools/provider-kiro/settings";
5import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
6import * as Effect from "effect/Effect";
7import * as FileSystem from "effect/FileSystem";
8import * as Layer from "effect/Layer";
9import * as Path from "effect/Path";
10import * as Schema from "effect/Schema";
11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
12
13import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
14import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
15import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
16import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
17import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
18import {
19 type AcpReplayTranscript,
20 AcpReplayTranscriptDecodeError,
21 decodeAcpReplayTranscript,
22 makeAcpReplayCompletenessAssertion,
23 makeAcpReplayRuntime,
24} from "./AcpAdapterV2.testkit.ts";
25import { KIRO_PROVIDER, makeKiroAdapterV2 } from "@t3tools/provider-kiro/testing";
26
27const KIRO_REPLAY_SETTINGS = Schema.decodeSync(KiroSettings)({ enabled: true });
28
29function layerKiroProviderAdapterRegistryReplay(
30 transcript: AcpReplayTranscript,
31 options: { readonly replayGate?: ProviderReplayGate } = {},
32) {
33 const layerHost = TestProviderHost.layer().pipe(
34 Layer.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
35 );
36
37 return ProviderAdapterRegistry.layerFromAdaptersEffect(
38 Effect.gen(function* () {
39 const fileSystem = yield* FileSystem.FileSystem;
40 const path = yield* Path.Path;
41 const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
42 const replayDir = yield* fileSystem
43 .makeTempDirectory({ prefix: `t3-orchestration-v2-kiro-replay-${transcript.scenario}-` })
44 .pipe(Effect.orDie);
45 const statusPath = path.join(replayDir, "status.json");
46 const scriptPath = yield* path
47 .fromFileUrl(new URL("../../../scripts/acp-replay-agent.ts", import.meta.url))
48 .pipe(Effect.orDie);
49 const adapter = yield* makeKiroAdapterV2({
50 instanceId: ProviderInstanceId.make("kiro"),
51 settings: KIRO_REPLAY_SETTINGS,
52 environment: {},
53 selfInvocation: yield* resolveSelfInvocation(),
54 makeRuntime: makeAcpReplayRuntime({
55 transcript,
56 statusPath,
57 scriptPath,
58 childProcessSpawner,
59 fileSystem,
60 ...(options.replayGate === undefined ? {} : { replayGate: options.replayGate }),
61 }),
62 assertComplete: makeAcpReplayCompletenessAssertion(fileSystem, statusPath, transcript),
63 });
64 return [adapter];
65 }),
66 ).pipe(
67 Layer.provide(
68 Layer.mergeAll(layerHost, NodeServices.layer, NodeCrypto.layer, IdAllocator.layer),
69 ),
70 // Held inbound lines must not outlive the scenario and wedge teardown.
71 Layer.merge(
72 Layer.effectDiscard(
73 Effect.addFinalizer(() => Effect.sync(() => options.replayGate?.releaseAll())),
74 ),
75 ),
76 );
77}
78
79export const KiroOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
80 AcpReplayTranscript,
81 AcpReplayTranscriptDecodeError
82> = {
83 driver: KIRO_PROVIDER,
84 decodeTranscript: (transcript) => decodeAcpReplayTranscript(transcript, KIRO_PROVIDER),
85 makeProviderAdapterRegistryLayer: layerKiroProviderAdapterRegistryReplay,
86};