1import * as ScriptProgress from "@t3tools/provider-core/server/ScriptProgress";
2import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
3import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { assert, it } from "@effect/vitest";
6import {
7 EnvironmentId,
8 CommandId,
9 MessageId,
10 type OrchestrationV2ThreadProjection,
11 ProjectId,
12 ProviderDriverKind,
13 ProviderInstanceId,
14 ThreadId,
15} from "@t3tools/contracts";
16import * as Console from "effect/Console";
17import * as Effect from "effect/Effect";
18import * as Layer from "effect/Layer";
19import * as ResetCreditCoordinator from "../provider/resetCreditCoordinator.ts";
20import { FetchHttpClient } from "effect/http";
21import { describe } from "vite-plus/test";
22
23import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
24import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
25import * as HostPowerMonitor from "../background/HostPowerMonitor.ts";
26import * as ServerConfig from "../config.ts";
27import * as SqlitePersistence from "../persistence/Sqlite.ts";
28import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts";
29import * as CodexInstallation from "../provider/CodexInstallation.ts";
30import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
31import * as ModelManifest from "../provider/ModelManifest.ts";
32import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
33import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
34import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
35import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
36import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
37import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
38import * as ServerSettings from "../serverSettings.ts";
39import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
40import * as VcsProcess from "../vcs/VcsProcess.ts";
41import * as Orchestrator from "./Orchestrator.ts";
42import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
43import * as RuntimeLayer from "./runtimeLayer.ts";
44import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
45import { GROK_MODEL_SELECTION } from "./testkit/fixtures/shared.ts";
46
47const layerPlatformTest = Layer.merge(
48 NodeServices.layer,
49 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
50 resolveLink: () => Effect.die("unused title link"),
51 }),
52);
53
54const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
55 prefix: "t3-grok-v2-live-",
56});
57
58const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
59 Layer.provide(VcsProcess.layer),
60 Layer.provide(layerServerConfig),
61 Layer.provide(layerPlatformTest),
62);
63
64const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
65
66const layerServerSettings = ServerSettings.layerTest({
67 providerInstances: {
68 [ProviderInstanceId.make("grok")]: {
69 driver: ProviderDriverKind.make("grok"),
70 enabled: true,
71 },
72 },
73});
74const layerBackgroundPolicy = BackgroundPolicy.layer.pipe(
75 Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())),
76 Layer.provide(layerServerSettings),
77);
78const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe(
79 Layer.provide(
80 Layer.mergeAll(
81 layerServerConfig.pipe(Layer.provide(layerPlatformTest)),
82 layerServerSettings,
83 ServerSecretStore.layer.pipe(
84 Layer.provide(layerServerConfig),
85 Layer.provide(layerPlatformTest),
86 ),
87 NodeServices.layer,
88 FetchHttpClient.layer,
89 OpenCodeRuntime.layer.pipe(
90 Layer.provide(OpenCodeServerLedger.layerTest),
91 Layer.provide(layerPlatformTest),
92 ),
93 Layer.succeed(
94 ProviderEventLoggers.ProviderEventLoggers,
95 ProviderEventLoggers.NoOpProviderEventLoggers,
96 ),
97 ProviderLatestVersions.layer,
98 ModelManifest.layerTest,
99 AntigravityInstallation.AntigravityInstallation.layer.pipe(
100 Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))),
101 Layer.provide(FetchHttpClient.layer),
102 Layer.provide(layerPlatformTest),
103 ),
104 // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex.
105 Layer.mock(CodexInstallation.CodexInstallation)({
106 managedDirectory: "unused-managed-installation",
107 }),
108 Layer.succeed(ServerEnvironment.ServerEnvironmentIdentity, {
109 getEnvironmentId: Effect.succeed(
110 EnvironmentId.make("00000000-0000-4000-8000-000000000001"),
111 ),
112 }),
113 ),
114 ),
115);
116
117const layerLive = RuntimeLayer.layer.pipe(
118 Layer.provide(ProviderTurnStartServiceTestkit.layer),
119 Layer.provide(McpSessionRegistryTestkit.layer),
120 Layer.provide(SqlitePersistence.layerMemory),
121 Layer.provide(layerCheckpointStore),
122 Layer.provide(layerServerConfig),
123 Layer.provide(layerServerSettings),
124 Layer.provide(layerProviderInstanceRegistry),
125 Layer.provide(ResetCreditCoordinator.layer),
126 Layer.provide(layerBackgroundPolicy),
127 Layer.provide(layerPlatformTest),
128 Layer.provide(Layer.merge(McpProviderSessions.layer, ScriptProgress.layerUnavailable)),
129);
130
131const waitForIdle = Effect.fn("GrokOrchestratorV2Live.waitForIdle")(function* (threadId: ThreadId) {
132 const orchestrator = yield* Orchestrator.OrchestratorV2;
133 for (let attempt = 0; attempt < 600; attempt += 1) {
134 const projection = yield* orchestrator.getThreadProjection(threadId);
135 if (
136 projection.runs.length > 0 &&
137 projection.runs.every(
138 (run) => !["queued", "starting", "running", "waiting"].includes(run.status),
139 )
140 ) {
141 return projection;
142 }
143 yield* Effect.sleep("500 millis");
144 }
145 return yield* Effect.die(new Error(`Timed out waiting for Grok thread ${threadId}.`));
146});
147
148describe.runIf(process.env.T3_GROK_LIVE_ORCHESTRATOR === "1")("Grok V2 live orchestrator", () => {
149 it.live(
150 "forks through portable context using real Grok ACP agents",
151 () =>
152 Effect.gen(function* () {
153 const orchestrator = yield* Orchestrator.OrchestratorV2;
154 const projectId = ProjectId.make("project:grok-live-portable-fork");
155 const sourceThreadId = ThreadId.make("thread:grok-live-portable-fork:source");
156 const targetThreadId = ThreadId.make("thread:grok-live-portable-fork:target");
157 const marker = "GROK_LIVE_PORTABLE_FORK_7H3Q";
158
159 yield* orchestrator.dispatch({
160 type: "thread.create",
161 createdBy: "user",
162 creationSource: "web",
163 commandId: CommandId.make("command:grok-live-portable-fork:create"),
164 threadId: sourceThreadId,
165 projectId,
166 title: "Grok live portable fork source",
167 modelSelection: GROK_MODEL_SELECTION,
168 runtimeMode: "full-access",
169 interactionMode: "default",
170 branch: null,
171 worktreePath: null,
172 });
173 yield* Console.log("Grok live source thread created; dispatching source prompt.");
174 yield* orchestrator.dispatch({
175 type: "message.dispatch",
176 createdBy: "user",
177 creationSource: "web",
178 commandId: CommandId.make("command:grok-live-portable-fork:source"),
179 threadId: sourceThreadId,
180 messageId: MessageId.make("message:grok-live-portable-fork:source"),
181 text: `Remember this opaque marker. Respond with exactly: ${marker}`,
182 attachments: [],
183 modelSelection: GROK_MODEL_SELECTION,
184 dispatchMode: { type: "start_immediately" },
185 });
186 yield* Console.log("Grok live source prompt dispatched; waiting for completion.");
187 const sourceProjection = yield* waitForIdle(sourceThreadId);
188 yield* Console.log("Grok live source turn completed; dispatching portable fork.");
189
190 yield* orchestrator.dispatch({
191 type: "thread.fork",
192 createdBy: "user",
193 creationSource: "web",
194 commandId: CommandId.make("command:grok-live-portable-fork:fork"),
195 sourceThreadId,
196 targetThreadId,
197 sourcePoint: { type: "latest_stable" },
198 title: "Grok live portable fork target",
199 });
200 yield* orchestrator.dispatch({
201 type: "message.dispatch",
202 createdBy: "user",
203 creationSource: "web",
204 commandId: CommandId.make("command:grok-live-portable-fork:target"),
205 threadId: targetThreadId,
206 messageId: MessageId.make("message:grok-live-portable-fork:target"),
207 text: "Return the opaque marker from the transferred conversation. Respond with only the marker.",
208 attachments: [],
209 modelSelection: GROK_MODEL_SELECTION,
210 dispatchMode: { type: "start_immediately" },
211 });
212 const targetProjection = yield* waitForIdle(targetThreadId);
213 yield* Console.log("Grok live portable fork target completed.");
214
215 const assistantText = (projection: OrchestrationV2ThreadProjection) =>
216 projection.messages
217 .filter((message) => message.role === "assistant")
218 .map((message) => message.text)
219 .join("\n");
220
221 assert.deepEqual(
222 sourceProjection.runs.map((run) => [run.providerInstanceId, run.status]),
223 [["grok", "completed"]],
224 );
225 assert.deepEqual(
226 targetProjection.runs.map((run) => [run.providerInstanceId, run.status]),
227 [["grok", "completed"]],
228 );
229 assert.deepEqual(
230 targetProjection.contextTransfers.map((transfer) => [
231 transfer.type,
232 transfer.status,
233 transfer.resolution?.strategy,
234 ]),
235 [["fork", "consumed", "portable_context"]],
236 );
237 assert.deepEqual(
238 targetProjection.contextHandoffs.map((handoff) => handoff.strategy),
239 ["full_thread_summary"],
240 );
241 assert.include(targetProjection.contextHandoffs[0]?.summaryText ?? "", marker);
242 assert.include(assistantText(targetProjection), marker);
243 }).pipe(Effect.provide(layerLive), Effect.scoped),
244 360_000,
245 );
246});