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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

T3 Code is an "agent harness control surface". It enables control of the agents on your machine with a best-in-class mobile app ([iOS](https://apps.apple.com/us/app/t3-code-remote-claude-more/id6787819824), [Android](https://play.google.com/store/apps/details?id=com.t3tools.t3code)), [web app](https://app.t3.codes) and [Electron-based desktop app](https://t3.codes).

Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, and Google Antigravity. If they're set up on your computer, T3 Code can control them.
Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, Oh My Pi (omp), OpenCode, and Google Antigravity. If they're set up on your computer, T3 Code can control them.

## "Wait, what are you selling me?"

Expand All @@ -13,13 +13,14 @@ We wanted something performant, remote-ready, and truly open. If we ever go the
## Installation

> [!WARNING]
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, and Antigravity. Install and authenticate at least one provider before use:
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, Oh My Pi (omp), OpenCode, and Antigravity. Install and authenticate at least one provider before use:
>
> - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login`
> - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login`
> - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login`
> - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login`
> - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login`
> - Oh My Pi (omp): install [Oh My Pi](https://github.com/can1357/oh-my-pi) and run `omp`
> - Antigravity: enable it in Settings, then use **Install Antigravity** and **Sign in with Google**. No CLI is required.

### Try it out (install-free)
Expand Down
13 changes: 13 additions & 0 deletions apps/mobile/src/components/ProviderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}

if (props.provider === "omp") {
return (
<Svg width={size} height={size} viewBox="0 0 800 800" fill="none">
<Path
fill={mono}
fillRule="evenodd"
d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"
/>
<Path fill={mono} d="M517.36 400H634.72V634.72H517.36Z" />
</Svg>
);
}

if (props.provider === "cursor") {
return (
<Svg width={size} height={size} viewBox="0 0 466.73 532.09" fill="none">
Expand Down
200 changes: 180 additions & 20 deletions apps/server/scripts/acp-mock-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ const emitStaleXAiPromptCompleteBeforeSecondHang =
const emitOverlappingXAiPromptCompleteOutOfOrder =
process.env.T3_ACP_EMIT_OVERLAPPING_XAI_PROMPT_COMPLETE_OUT_OF_ORDER === "1";
const failPrompt = process.env.T3_ACP_FAIL_PROMPT === "1";
// Oh My Pi shape: serve omp-style modes (default/plan) and configOptions
// (mode/model/thinking) instead of the default/Grok shapes.
const ompShapes = process.env.T3_ACP_OMP_SHAPES === "1";
const emitTaskTool = process.env.T3_ACP_EMIT_TASK_TOOL === "1";
const emitElicitation = process.env.T3_ACP_EMIT_ELICITATION === "1";
const emitTaskToolBatch = process.env.T3_ACP_EMIT_TASK_TOOL_BATCH === "1";
const emitTaskToolFail = process.env.T3_ACP_EMIT_TASK_TOOL_FAIL === "1";
const failSetConfigOption = process.env.T3_ACP_FAIL_SET_CONFIG_OPTION === "1";
const setConfigOptionDelayMs = Number(process.env.T3_ACP_SET_CONFIG_OPTION_DELAY_MS ?? "0");
const exitOnSetConfigOption = process.env.T3_ACP_EXIT_ON_SET_CONFIG_OPTION === "1";
const promptResponseText = process.env.T3_ACP_PROMPT_RESPONSE_TEXT;
const initialGrokReasoningEffort =
Expand All @@ -67,8 +75,13 @@ const permissionRequestCount = Math.max(
);
const sessionId = "mock-session-1";

let currentModeId = antigravityProfile ? "default" : "ask";
let currentModelId = antigravityProfile ? "gemini-test-low" : "default";
let currentModeId = ompShapes || antigravityProfile ? "default" : "ask";
let currentModelId = ompShapes
? "zhipu-coding-plan/glm-5.3"
: antigravityProfile
? "gemini-test-low"
: "default";
let currentThinking = "high";
let parameterizedModelPicker = false;
let currentReasoning = "medium";
let currentContext = "272k";
Expand Down Expand Up @@ -114,6 +127,49 @@ process.once("exit", (code) => {
});

function configOptions(): ReadonlyArray<AcpSchema.SessionConfigOption> {
if (ompShapes) {
return [
{
id: "mode",
name: "Mode",
category: "mode",
type: "select",
currentValue: currentModeId,
options: availableModes.map((mode) => ({
value: mode.id,
name: mode.name,
...(mode.description ? { description: mode.description } : {}),
})),
},
{
id: "model",
name: "Model",
category: "model",
type: "select",
currentValue: currentModelId,
options: [
{ value: "zhipu-coding-plan/glm-5.3", name: "GLM 5.3" },
{ value: "anthropic/claude-opus-4-6", name: "Claude Opus 4.6" },
{ value: "openai/gpt-5.4", name: "GPT-5.4" },
],
},
{
id: "thinking",
name: "Thinking",
category: "thought_level",
type: "select",
currentValue: currentThinking,
options: [
{ value: "off", name: "Off" },
{ value: "low", name: "Low" },
{ value: "medium", name: "Medium" },
{ value: "high", name: "High" },
{ value: "max", name: "Max" },
],
},
];
}

if (antigravityProfile) {
return [
{
Expand Down Expand Up @@ -298,29 +354,42 @@ const antigravityModels = [
{ modelId: "gemini-test-high", name: "Gemini Test High" },
] satisfies ReadonlyArray<AcpSchema.ModelInfo>;

const availableModes: ReadonlyArray<AcpSchema.SessionMode> = antigravityProfile
const availableModes: ReadonlyArray<AcpSchema.SessionMode> = ompShapes
? [
{ id: "default", name: "Default" },
{ id: "auto_edit", name: "Auto edit" },
{ id: "yolo", name: "YOLO" },
]
: [
{
id: "ask",
name: "Ask",
description: "Request permission before making any changes",
},
{
id: "architect",
name: "Architect",
description: "Design and plan software systems without implementation",
id: "default",
name: "Default",
description: "Write and modify code with full tool access",
},
{
id: "code",
name: "Code",
description: "Write and modify code with full tool access",
id: "plan",
name: "Plan",
description: "Design and plan without making changes",
},
];
]
: antigravityProfile
? [
{ id: "default", name: "Default" },
{ id: "auto_edit", name: "Auto edit" },
{ id: "yolo", name: "YOLO" },
]
: [
{
id: "ask",
name: "Ask",
description: "Request permission before making any changes",
},
{
id: "architect",
name: "Architect",
description: "Design and plan software systems without implementation",
},
{
id: "code",
name: "Code",
description: "Write and modify code with full tool access",
},
];

function modeState(): AcpSchema.SessionModeState {
return {
Expand Down Expand Up @@ -549,6 +618,9 @@ const program = Effect.gen(function* () {

yield* agent.handleSetSessionConfigOption((request) =>
Effect.gen(function* () {
if (Number.isFinite(setConfigOptionDelayMs) && setConfigOptionDelayMs > 0) {
yield* Effect.sleep(`${setConfigOptionDelayMs} millis`);
}
if (exitOnSetConfigOption) {
return yield* Effect.sync(() => {
process.exit(7);
Expand Down Expand Up @@ -578,6 +650,9 @@ const program = Effect.gen(function* () {
if (request.configId === "fast") {
currentFast = request.value === true || request.value === "true";
}
if (request.configId === "thinking" && typeof request.value === "string") {
currentThinking = request.value;
}
return {
configOptions: configOptions(),
};
Expand Down Expand Up @@ -885,6 +960,91 @@ const program = Effect.gen(function* () {
return { stopReason: "end_turn" };
}

if (emitElicitation) {
// Mirror real omp (official @agent-client-protocol/sdk): the request
// goes out as the extension method `elicitation/create`, and the
// response is the FLAT shape { action: "accept", content } — not
// effect-acp's nested ElicitationResponse.
const result = yield* agent.client.extRequest("elicitation/create", {
sessionId: requestedSessionId,
mode: "form",
message: "Approve this action?",
requestedSchema: {
type: "object",
properties: {
value: {
type: "string",
title: "Decision",
enum: ["Approve", "Deny"],
},
},
required: ["value"],
},
});
const action =
typeof result === "object" && result !== null && "action" in result
? result.action
: undefined;
yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: `elicitation ${typeof action === "string" ? action : "unknown"}`,
},
},
});
return { stopReason: "end_turn" };
}

if (emitTaskTool || emitTaskToolBatch || emitTaskToolFail) {
const toolCallId = "task-tool-call-1";
const rawInput = emitTaskToolBatch
? {
tasks: [
{ agent: "scout", task: "Research the codebase layout", effort: "low" },
{ agent: "worker", task: "Implement the feature", effort: "high" },
],
context: "shared batch context",
}
: { agent: "worker", task: "Implement the feature", effort: "high" };

yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "tool_call",
toolCallId,
title: "Task",
kind: "other",
status: "pending",
rawInput,
},
});

yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId,
status: emitTaskToolFail ? "failed" : "completed",
rawOutput: {
output: emitTaskToolFail ? "subagent failed to finish" : "subagent finished the work",
},
},
});

yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "task tool done" },
},
});

return { stopReason: "end_turn" };
}

if (emitToolCalls) {
const toolCallId = "tool-call-1";

Expand Down
Loading
Loading