Skip to content

Commit 8e6f422

Browse files
[codex] Fix main CI Effect test runtimes (pingdotgg#3008)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 38ea6d4 commit 8e6f422

4 files changed

Lines changed: 173 additions & 169 deletions

File tree

apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import * as ManagedRuntime from "effect/ManagedRuntime";
2828
import * as PubSub from "effect/PubSub";
2929
import * as Scope from "effect/Scope";
3030
import * as Stream from "effect/Stream";
31+
import { it as effectIt } from "@effect/vitest";
3132
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
3233

3334
import { deriveServerPaths, ServerConfig } from "../../config.ts";
@@ -890,70 +891,79 @@ describe("ProviderCommandReactor", () => {
890891
});
891892
});
892893

893-
it("rejects changing models after start when the provider requires a new thread", async () => {
894-
const harness = await createHarness({ requiresNewThreadForModelChange: true });
895-
const now = "2026-01-01T00:00:00.000Z";
894+
effectIt.effect(
895+
"rejects changing models after start when the provider requires a new thread",
896+
() =>
897+
Effect.gen(function* () {
898+
const harness = yield* Effect.promise(() =>
899+
createHarness({ requiresNewThreadForModelChange: true }),
900+
);
901+
const now = "2026-01-01T00:00:00.000Z";
896902

897-
await Effect.runPromise(
898-
harness.engine.dispatch({
899-
type: "thread.turn.start",
900-
commandId: CommandId.make("cmd-turn-start-restricted-1"),
901-
threadId: ThreadId.make("thread-1"),
902-
message: {
903-
messageId: asMessageId("user-message-restricted-1"),
904-
role: "user",
905-
text: "first",
906-
attachments: [],
907-
},
908-
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
909-
runtimeMode: "approval-required",
910-
createdAt: now,
911-
}),
912-
);
903+
yield* harness.engine.dispatch({
904+
type: "thread.turn.start",
905+
commandId: CommandId.make("cmd-turn-start-restricted-1"),
906+
threadId: ThreadId.make("thread-1"),
907+
message: {
908+
messageId: asMessageId("user-message-restricted-1"),
909+
role: "user",
910+
text: "first",
911+
attachments: [],
912+
},
913+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
914+
runtimeMode: "approval-required",
915+
createdAt: now,
916+
});
913917

914-
await waitFor(() => harness.sendTurn.mock.calls.length === 1);
918+
yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
915919

916-
await Effect.runPromise(
917-
harness.engine.dispatch({
918-
type: "thread.turn.start",
919-
commandId: CommandId.make("cmd-turn-start-restricted-2"),
920-
threadId: ThreadId.make("thread-1"),
921-
message: {
922-
messageId: asMessageId("user-message-restricted-2"),
923-
role: "user",
924-
text: "second",
925-
attachments: [],
926-
},
927-
modelSelection: {
928-
instanceId: ProviderInstanceId.make("codex"),
929-
model: "gpt-5.1-codex",
930-
},
931-
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
932-
runtimeMode: "approval-required",
933-
createdAt: now,
934-
}),
935-
);
920+
yield* harness.engine.dispatch({
921+
type: "thread.turn.start",
922+
commandId: CommandId.make("cmd-turn-start-restricted-2"),
923+
threadId: ThreadId.make("thread-1"),
924+
message: {
925+
messageId: asMessageId("user-message-restricted-2"),
926+
role: "user",
927+
text: "second",
928+
attachments: [],
929+
},
930+
modelSelection: {
931+
instanceId: ProviderInstanceId.make("codex"),
932+
model: "gpt-5.1-codex",
933+
},
934+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
935+
runtimeMode: "approval-required",
936+
createdAt: now,
937+
});
936938

937-
await waitFor(async () => {
938-
const readModel = await harness.readModel();
939-
const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
940-
return (
941-
thread?.activities.some((activity) => activity.kind === "provider.turn.start.failed") ??
942-
false
943-
);
944-
});
939+
yield* Effect.promise(() =>
940+
waitFor(async () => {
941+
const readModel = await harness.readModel();
942+
const thread = readModel.threads.find(
943+
(entry) => entry.id === ThreadId.make("thread-1"),
944+
);
945+
return (
946+
thread?.activities.some(
947+
(activity) => activity.kind === "provider.turn.start.failed",
948+
) ?? false
949+
);
950+
}),
951+
);
945952

946-
expect(harness.sendTurn).toHaveBeenCalledTimes(1);
947-
const readModel = await harness.readModel();
948-
const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
949-
expect(
950-
thread?.activities.find((activity) => activity.kind === "provider.turn.start.failed"),
951-
).toMatchObject({
952-
payload: {
953-
detail: expect.stringContaining("cannot switch models after the conversation has started"),
954-
},
955-
});
956-
});
953+
expect(harness.sendTurn).toHaveBeenCalledTimes(1);
954+
const readModel = yield* Effect.promise(() => harness.readModel());
955+
const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
956+
expect(
957+
thread?.activities.find((activity) => activity.kind === "provider.turn.start.failed"),
958+
).toMatchObject({
959+
payload: {
960+
detail: expect.stringContaining(
961+
"cannot switch models after the conversation has started",
962+
),
963+
},
964+
});
965+
}),
966+
);
957967

958968
it("starts a first turn on the requested provider instance even when it differs from the thread model", async () => {
959969
const harness = await createHarness({

apps/server/src/provider/Layers/GrokAdapter.test.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,21 @@ exec ${JSON.stringify(mockAgentCommand)} ${JSON.stringify(mockAgentPath)} "$@"
4545
return wrapperPath;
4646
}
4747

48-
async function waitForFileContent(filePath: string, attempts = 40): Promise<string> {
49-
const readAttempt = async (remainingAttempts: number): Promise<string> => {
50-
if (remainingAttempts <= 0) {
51-
throw new Error(`Timed out waiting for file content at ${filePath}`);
52-
}
53-
try {
54-
const raw = await readFile(filePath, "utf8");
48+
function waitForFileContent(filePath: string, attempts = 40): Effect.Effect<string> {
49+
const readAttempt = (remainingAttempts: number): Effect.Effect<string> =>
50+
Effect.gen(function* () {
51+
if (remainingAttempts <= 0) {
52+
return yield* Effect.die(new Error(`Timed out waiting for file content at ${filePath}`));
53+
}
54+
const raw = yield* Effect.tryPromise(() => readFile(filePath, "utf8")).pipe(
55+
Effect.orElseSucceed(() => ""),
56+
);
5557
if (raw.trim().length > 0) {
5658
return raw;
5759
}
58-
} catch {}
59-
await Effect.runPromise(Effect.sleep("25 millis"));
60-
return readAttempt(remainingAttempts - 1);
61-
};
60+
yield* Effect.sleep("25 millis");
61+
return yield* readAttempt(remainingAttempts - 1);
62+
});
6263
return readAttempt(attempts);
6364
}
6465

@@ -169,7 +170,7 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {
169170

170171
yield* adapter.stopSession(threadId);
171172

172-
const exitLog = yield* Effect.promise(() => waitForFileContent(exitLogPath));
173+
const exitLog = yield* waitForFileContent(exitLogPath);
173174
assert.include(exitLog, "SIGTERM");
174175
}),
175176
);
Lines changed: 54 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,60 @@
11
import * as NodeServices from "@effect/platform-node/NodeServices";
2+
import { describe, expect, it } from "@effect/vitest";
23
import * as Effect from "effect/Effect";
34
import * as FileSystem from "effect/FileSystem";
45
import * as Path from "effect/Path";
56
import * as Schema from "effect/Schema";
6-
import { describe, expect, it } from "vite-plus/test";
7-
import { ChildProcessSpawner } from "effect/unstable/process";
87
import { GrokSettings } from "@t3tools/contracts";
98

109
import { buildInitialGrokProviderSnapshot, checkGrokProviderStatus } from "./GrokProvider.ts";
1110

1211
const decodeGrokSettings = Schema.decodeSync(GrokSettings);
1312

14-
const runNode = <A, E>(
15-
effect: Effect.Effect<
16-
A,
17-
E,
18-
FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner
19-
>,
20-
): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer)));
21-
2213
describe("buildInitialGrokProviderSnapshot", () => {
23-
it("returns a disabled snapshot when settings.enabled is false", async () => {
24-
const snapshot = await Effect.runPromise(
25-
buildInitialGrokProviderSnapshot(decodeGrokSettings({ enabled: false })),
26-
);
27-
expect(snapshot.enabled).toBe(false);
28-
expect(snapshot.status).toBe("disabled");
29-
expect(snapshot.installed).toBe(false);
30-
expect(snapshot.message).toContain("disabled");
31-
});
14+
it.effect("returns a disabled snapshot when settings.enabled is false", () =>
15+
Effect.gen(function* () {
16+
const snapshot = yield* buildInitialGrokProviderSnapshot(
17+
decodeGrokSettings({ enabled: false }),
18+
);
19+
expect(snapshot.enabled).toBe(false);
20+
expect(snapshot.status).toBe("disabled");
21+
expect(snapshot.installed).toBe(false);
22+
expect(snapshot.message).toContain("disabled");
23+
}),
24+
);
3225

33-
it("returns a pending snapshot by default", async () => {
34-
const snapshot = await Effect.runPromise(
35-
buildInitialGrokProviderSnapshot(decodeGrokSettings({})),
36-
);
37-
expect(snapshot.enabled).toBe(true);
38-
expect(snapshot.installed).toBe(true);
39-
expect(snapshot.status).toBe("warning");
40-
expect(snapshot.version).toBeNull();
41-
expect(snapshot.message).toContain("Checking Grok");
42-
expect(snapshot.requiresNewThreadForModelChange).toBe(true);
43-
});
26+
it.effect("returns a pending snapshot by default", () =>
27+
Effect.gen(function* () {
28+
const snapshot = yield* buildInitialGrokProviderSnapshot(decodeGrokSettings({}));
29+
expect(snapshot.enabled).toBe(true);
30+
expect(snapshot.installed).toBe(true);
31+
expect(snapshot.status).toBe("warning");
32+
expect(snapshot.version).toBeNull();
33+
expect(snapshot.message).toContain("Checking Grok");
34+
expect(snapshot.requiresNewThreadForModelChange).toBe(true);
35+
}),
36+
);
4437
});
4538

46-
describe("checkGrokProviderStatus", () => {
47-
it("reports the binary as missing when the binary path does not resolve", async () => {
48-
const snapshot = await runNode(
49-
checkGrokProviderStatus(
39+
it.layer(NodeServices.layer)("checkGrokProviderStatus", (it) => {
40+
it.effect("reports the binary as missing when the binary path does not resolve", () =>
41+
Effect.gen(function* () {
42+
const snapshot = yield* checkGrokProviderStatus(
5043
decodeGrokSettings({
5144
enabled: true,
5245
binaryPath: "/definitely/not/installed/grok-binary",
5346
}),
54-
),
55-
);
56-
expect(snapshot.enabled).toBe(true);
57-
expect(snapshot.installed).toBe(false);
58-
expect(snapshot.status).toBe("error");
59-
expect(snapshot.message).toMatch(/not installed|not on PATH|Failed to execute/);
60-
});
47+
);
48+
expect(snapshot.enabled).toBe(true);
49+
expect(snapshot.installed).toBe(false);
50+
expect(snapshot.status).toBe("error");
51+
expect(snapshot.message).toMatch(/not installed|not on PATH|Failed to execute/);
52+
}),
53+
);
6154

62-
it("reports an installed CLI as unhealthy when --version exits non-zero", async () => {
63-
const snapshot = await runNode(
64-
Effect.scoped(
55+
it.effect("reports an installed CLI as unhealthy when --version exits non-zero", () =>
56+
Effect.gen(function* () {
57+
const snapshot = yield* Effect.scoped(
6558
Effect.gen(function* () {
6659
const fs = yield* FileSystem.FileSystem;
6760
const path = yield* Path.Path;
@@ -77,18 +70,18 @@ describe("checkGrokProviderStatus", () => {
7770
decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
7871
);
7972
}),
80-
),
81-
);
73+
);
8274

83-
expect(snapshot.enabled).toBe(true);
84-
expect(snapshot.installed).toBe(true);
85-
expect(snapshot.status).toBe("error");
86-
expect(snapshot.message).toContain("broken grok install");
87-
});
75+
expect(snapshot.enabled).toBe(true);
76+
expect(snapshot.installed).toBe(true);
77+
expect(snapshot.status).toBe("error");
78+
expect(snapshot.message).toContain("broken grok install");
79+
}),
80+
);
8881

89-
it("reports an error when ACP model discovery is unavailable", async () => {
90-
const snapshot = await runNode(
91-
Effect.scoped(
82+
it.effect("reports an error when ACP model discovery is unavailable", () =>
83+
Effect.gen(function* () {
84+
const snapshot = yield* Effect.scoped(
9285
Effect.gen(function* () {
9386
const fs = yield* FileSystem.FileSystem;
9487
const path = yield* Path.Path;
@@ -104,12 +97,12 @@ describe("checkGrokProviderStatus", () => {
10497
decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
10598
);
10699
}),
107-
),
108-
);
100+
);
109101

110-
expect(snapshot.status).toBe("error");
111-
expect(snapshot.installed).toBe(true);
112-
expect(snapshot.models.map((model) => model.slug)).toEqual(["grok-build"]);
113-
expect(snapshot.message).toContain("ACP startup failed");
114-
});
102+
expect(snapshot.status).toBe("error");
103+
expect(snapshot.installed).toBe(true);
104+
expect(snapshot.models.map((model) => model.slug)).toEqual(["grok-build"]);
105+
expect(snapshot.message).toContain("ACP startup failed");
106+
}),
107+
);
115108
});

0 commit comments

Comments
 (0)