Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions apps/server/src/provider/CodexDeveloperInstructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ For browser work, first call \`preview_status\`. If no automation-capable previe
Do not switch to global browser skills, Chrome, Node REPL browser automation, standalone Playwright, or agent-browser merely because the preview is initially closed or a first call fails. Use an alternative browser system only when the T3 preview tools are absent, the user explicitly requests another browser, or \`preview_open\` returns an explicit unsupported/unavailable error. A failed T3 preview tool call should be inspected and retried with corrected arguments when the error is actionable.
`;

export const CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS = `<collaboration_mode># Plan Mode (Conversational)
const CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS_BASE = `<collaboration_mode># Plan Mode (Conversational)

You work in 3 phases, and you should *chat your way* to a great plan before finalizing it. A great plan is very detailed-intent- and implementation-wise-so that it can be handed to another engineer or agent to be implemented right away. It must be **decision complete**, where the implementer does not need to make any decisions.

Expand Down Expand Up @@ -131,10 +131,9 @@ plan content should be human and agent digestible. The final plan must be plan-o
Do not ask "should I proceed?" in the final output. The user can easily switch out of Plan mode and request implementation if you have included a \`<proposed_plan>\` block in your response. Alternatively, they can decide to stay in Plan mode and continue refining the plan.

Only produce at most one \`<proposed_plan>\` block per turn, and only when you are presenting a complete spec.
${T3_CODE_BROWSER_TOOL_INSTRUCTIONS}
</collaboration_mode>`;

export const CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS = `<collaboration_mode># Collaboration Mode: Default
const CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS_BASE = `<collaboration_mode># Collaboration Mode: Default

You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.

Expand All @@ -145,14 +144,44 @@ Your active mode changes only when new developer instructions with a different \
The \`request_user_input\` tool is unavailable in Default mode. If you call it while in Default mode, it will return an error.

In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.
${T3_CODE_BROWSER_TOOL_INSTRUCTIONS}
</collaboration_mode>`;

function withOptionalT3PreviewInstructions(base: string, includeT3PreviewTools: boolean): string {
if (!includeT3PreviewTools) {
return base;
}
// Target the final closing tag only. Default-mode prose mentions
// `</collaboration_mode>` inside an inline code span earlier in the body.
const closingTag = "</collaboration_mode>";
const closingTagIndex = base.lastIndexOf(closingTag);
if (closingTagIndex === -1) {
return base;
}
return `${base.slice(0, closingTagIndex)}${T3_CODE_BROWSER_TOOL_INSTRUCTIONS}
${closingTag}`;
}

/** Plan-mode developer instructions without T3 Preview routing. */
export const CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS = CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS_BASE;

/** Default-mode developer instructions without T3 Preview routing. */
export const CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS =
CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS_BASE;

export interface CodexRuntimeInfo {
readonly model: string;
readonly reasoningEffort: string;
}

export interface BuildCodexDeveloperInstructionsOptions {
/**
* When true, append T3 Preview collaborative-browser routing. Callers must
* pass the actual t3-code MCP mount state so instructions cannot advertise
* tools the session does not have.
*/
readonly includeT3PreviewTools?: boolean;
}

// Values come from trusted config, but keep the block single-line regardless.
function toSingleLine(value: string): string {
return value.replaceAll(/\s+/g, " ").trim();
Expand All @@ -161,12 +190,17 @@ function toSingleLine(value: string): string {
export function buildCodexDeveloperInstructions(
interactionMode: ProviderInteractionMode,
runtime: CodexRuntimeInfo,
options?: BuildCodexDeveloperInstructionsOptions,
): string {
const base =
interactionMode === "plan"
? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS
: CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS;
return `${base}
? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS_BASE
: CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS_BASE;
const modeInstructions = withOptionalT3PreviewInstructions(
base,
options?.includeT3PreviewTools === true,
);
return `${modeInstructions}

<runtime_info>In case you're asked: you are running in T3 Code through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`;
}
60 changes: 60 additions & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
import {
ApprovalRequestId,
ClaudeSettings,
EnvironmentId,
ProviderDriverKind,
ProviderItemId,
ProviderRuntimeEvent,
Expand All @@ -34,6 +35,7 @@ import * as TestClock from "effect/testing/TestClock";

import { attachmentRelativePath } from "../../attachmentStore.ts";
import { ServerConfig } from "../../config.ts";
import * as McpProviderSession from "../../mcp/McpProviderSession.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { ProviderAdapterProcessError, ProviderAdapterValidationError } from "../Errors.ts";
import type { ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts";
Expand Down Expand Up @@ -298,6 +300,64 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect("omits t3-code MCP servers when no provider MCP session exists", () => {
const harness = makeHarness();
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
yield* adapter.startSession({
threadId: THREAD_ID,
provider: ProviderDriverKind.make("claudeAgent"),
runtimeMode: "full-access",
});

const createInput = harness.getLastCreateQueryInput();
assert.equal(createInput?.options.mcpServers, undefined);
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
});

it.effect("mounts t3-code MCP servers when a provider MCP session exists", () => {
const harness = makeHarness();
const threadId = ThreadId.make("thread-claude-mcp");
McpProviderSession.setMcpProviderSession({
environmentId: EnvironmentId.make("environment-1"),
threadId,
providerSessionId: "provider-session-1",
providerInstanceId: ProviderInstanceId.make("claudeAgent"),
endpoint: "http://127.0.0.1:43123/mcp",
authorizationHeader: "Bearer preview-token",
});

return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
try {
yield* adapter.startSession({
threadId,
provider: ProviderDriverKind.make("claudeAgent"),
runtimeMode: "full-access",
});

const createInput = harness.getLastCreateQueryInput();
assert.deepEqual(createInput?.options.mcpServers, {
"t3-code": {
type: "http",
url: "http://127.0.0.1:43123/mcp",
headers: {
Authorization: "Bearer preview-token",
},
},
});
} finally {
McpProviderSession.clearMcpProviderSession(threadId);
}
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
});

it.effect("retains Claude session startup causes without exposing their messages", () => {
const cause = new Error("credential material that must remain in the cause chain");
const layer = Layer.effect(
Expand Down
59 changes: 59 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as NodePath from "node:path";
import {
ApprovalRequestId,
CodexSettings,
EnvironmentId,
EventId,
ProviderDriverKind,
ProviderInstanceId,
Expand Down Expand Up @@ -35,6 +36,7 @@ import * as Stream from "effect/Stream";
import * as CodexErrors from "effect-codex-app-server/errors";

import { ServerConfig } from "../../config.ts";
import * as McpProviderSession from "../../mcp/McpProviderSession.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { ProviderAdapterValidationError } from "../Errors.ts";
import type { CodexAdapterShape } from "../Services/CodexAdapter.ts";
Expand Down Expand Up @@ -288,6 +290,63 @@ validationLayer("CodexAdapterLive validation", (it) => {
});
}),
);

it.effect("omits t3-code MCP config when no provider MCP session exists", () =>
Effect.gen(function* () {
validationRuntimeFactory.factory.mockClear();
const adapter = yield* CodexAdapter;
const threadId = asThreadId("thread-no-mcp");

yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId,
runtimeMode: "full-access",
});

const options = validationRuntimeFactory.factory.mock.calls[0]?.[0] as
| CodexSessionRuntimeOptions
| undefined;
NodeAssert.equal(options?.appServerArgs, undefined);
NodeAssert.equal(options?.environment, undefined);
}),
);

it.effect("mounts t3-code MCP config when a provider MCP session exists", () =>
Effect.gen(function* () {
validationRuntimeFactory.factory.mockClear();
const adapter = yield* CodexAdapter;
const threadId = asThreadId("thread-with-mcp");
McpProviderSession.setMcpProviderSession({
environmentId: EnvironmentId.make("environment-1"),
threadId,
providerSessionId: "provider-session-1",
providerInstanceId: ProviderInstanceId.make("codex"),
endpoint: "http://127.0.0.1:43123/mcp",
authorizationHeader: "Bearer preview-token",
});

try {
yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId,
runtimeMode: "full-access",
});

const options = validationRuntimeFactory.factory.mock.calls[0]?.[0] as
| CodexSessionRuntimeOptions
| undefined;
NodeAssert.deepStrictEqual(options?.appServerArgs, [
"-c",
"mcp_servers.t3-code.url=http://127.0.0.1:43123/mcp",
"-c",
'mcp_servers.t3-code.bearer_token_env_var="T3_MCP_BEARER_TOKEN"',
]);
NodeAssert.equal(options?.environment?.T3_MCP_BEARER_TOKEN, "preview-token");
} finally {
McpProviderSession.clearMcpProviderSession(threadId);
}
}),
);
});

const sessionRuntimeFactory = makeRuntimeFactory();
Expand Down
Loading
Loading