Skip to content

Commit ddcfdfb

Browse files
committed
test: prove Codex structured and select semantics
Assert exact skill/mention paths survive turn/steer and compile-check that generated request_user_input questions have no multi-select field while MCP array elicitation remains multi-select. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f047b252-1029-4093-9a8e-53456b7dd740
1 parent be34a80 commit ddcfdfb

4 files changed

Lines changed: 35 additions & 2 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import * as Schema from "effect/Schema";
3333
import * as Scope from "effect/Scope";
3434
import * as Stream from "effect/Stream";
3535
import * as CodexErrors from "effect-codex-app-server/errors";
36+
import * as EffectCodexSchema from "effect-codex-app-server/schema";
3637

3738
import { ServerConfig } from "../../config.ts";
3839
import { ServerSettingsService } from "../../serverSettings.ts";
@@ -58,6 +59,9 @@ const asThreadId = (value: string): ThreadId => ThreadId.make(value);
5859
const asTurnId = (value: string): TurnId => TurnId.make(value);
5960
const asEventId = (value: string): EventId => EventId.make(value);
6061
const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value);
62+
const generatedRequestUserInputSupportsMultiSelect: "multiSelect" extends keyof EffectCodexSchema.ToolRequestUserInputParams__ToolRequestUserInputQuestion
63+
? true
64+
: false = false;
6165

6266
class FakeCodexRuntime implements CodexSessionRuntimeShape {
6367
private readonly eventQueue = Effect.runSync(Queue.unbounded<ProviderEvent>());
@@ -1145,6 +1149,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
11451149
const events = Array.from(yield* Fiber.join(eventsFiber));
11461150
NodeAssert.equal(events[0]?.type, "user-input.requested");
11471151
if (events[0]?.type === "user-input.requested") {
1152+
NodeAssert.equal(generatedRequestUserInputSupportsMultiSelect, false);
11481153
NodeAssert.equal(events[0].requestId, "req-user-input-1");
11491154
NodeAssert.equal(events[0].payload.questions[0]?.id, "sandbox_mode");
11501155
NodeAssert.equal(events[0].payload.questions[0]?.multiSelect, false);

apps/server/src/provider/Layers/CodexAdapter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ function toUserInputQuestions(questions: ReadonlyArray<CodexToolUserInputQuestio
374374
options,
375375
allowOther: question.isOther ?? options.length === 0,
376376
isSecret: question.isSecret ?? false,
377+
// The generated request_user_input question has no multi-select field.
378+
// MCP elicitation array schemas are projected separately with multiSelect=true.
377379
multiSelect: false,
378380
};
379381
})

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,38 @@ describe("Codex turn steering", () => {
287287
threadId: "provider-thread-1",
288288
expectedTurnId: "turn-active",
289289
clientUserMessageId: "message-2",
290-
turnInput: [{ type: "text", text: "Use the other approach" }],
290+
turnInput: [
291+
{ type: "text", text: "Use the other approach" },
292+
{
293+
type: "skill",
294+
name: "frontend-design",
295+
path: "/skills/frontend-design/SKILL.md",
296+
},
297+
{
298+
type: "mention",
299+
name: "App.tsx",
300+
path: "apps/web/src/App.tsx",
301+
},
302+
],
291303
});
292304

293305
NodeAssert.deepStrictEqual(params, {
294306
threadId: "provider-thread-1",
295307
expectedTurnId: "turn-active",
296308
clientUserMessageId: "message-2",
297-
input: [{ type: "text", text: "Use the other approach" }],
309+
input: [
310+
{ type: "text", text: "Use the other approach" },
311+
{
312+
type: "skill",
313+
name: "frontend-design",
314+
path: "/skills/frontend-design/SKILL.md",
315+
},
316+
{
317+
type: "mention",
318+
name: "App.tsx",
319+
path: "apps/web/src/App.tsx",
320+
},
321+
],
298322
});
299323
}),
300324
);

docs/operations/workplace-fork.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Typed app-server callbacks cover MCP elicitation, granular permission approval,
4343

4444
Codex question metadata is preserved for optionless free text, Other answers, secrets, mixed question sets, and MCP multi-select fields. Secret answers use a password control and are never rendered as visible composer text.
4545

46+
The generated `item/tool/requestUserInput` question type has no multi-select field and is intentionally projected as single-select. Multi-select is native for MCP elicitation array schemas and is preserved there; a compile-time schema assertion guards this protocol distinction.
47+
4648
Composer `$skill` selections and local markdown file links are resolved against the active provider snapshot and sent as native Codex `skill` and `mention` user-input entries. The original text remains intact for display and non-Codex providers; disabled/unknown skills and external links are not promoted to trusted structured inputs.
4749

4850
The command palette exposes native MCP management for an active Codex thread. Inventory, OAuth initiation/completion notifications, resource reads, direct tool invocation, server reload, and per-server enablement all use the shared app-server connection. Direct invocation requires an explicit per-server trust confirmation each time; T3 does not persist that confirmation or bypass Codex approval policy.

0 commit comments

Comments
 (0)