Skip to content
Closed
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Eight slots. Every abstraction is swappable.
| Slot | Default | Alternatives |
| --------- | ----------- | ------------------------ |
| Runtime | tmux | docker, k8s, process |
| Agent | claude-code | codex, aider, opencode |
| Agent | claude-code | codex, aider, acpx, opencode |
| Workspace | worktree | clone |
| Tracker | github | linear |
| SCM | github | — |
Expand Down
3 changes: 2 additions & 1 deletion agent-orchestrator.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ port: 3000
# Default plugins (these are the defaults — you can omit this section)
defaults:
runtime: tmux # tmux | process | docker | kubernetes | ssh | e2b
agent: claude-code # claude-code | codex | aider | goose | custom
agent: claude-code # claude-code | codex | aider | acpx | goose | custom
# orchestrator:
# agent: claude-code
# worker:
Expand Down Expand Up @@ -62,6 +62,7 @@ projects:
# agentConfig:
# permissions: skip # --dangerously-skip-permissions
# model: opus
# acpxAgent: pi # when agent: acpx (currently only "pi" is supported)

# Optional role-specific agent overrides
# orchestrator:
Expand Down
4 changes: 2 additions & 2 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Every abstraction is a swappable plugin. All interfaces are defined in [`package
| Slot | Interface | Default | Alternatives |
| --------- | ----------- | ------------- | ---------------------------------------- |
| Runtime | `Runtime` | `tmux` | `process`, `docker`, `k8s`, `ssh`, `e2b` |
| Agent | `Agent` | `claude-code` | `codex`, `aider`, `opencode` |
| Agent | `Agent` | `claude-code` | `codex`, `aider`, `acpx`, `opencode` |
| Workspace | `Workspace` | `worktree` | `clone` |
| Tracker | `Tracker` | `github` | `linear` |
| SCM | `SCM` | `github` | — |
Expand Down Expand Up @@ -107,7 +107,7 @@ agent-orchestrator/
│ ├── web/ # Next.js dashboard
│ ├── plugins/ # All plugin packages
│ │ ├── runtime-*/ # Runtime plugins (tmux, docker, k8s)
│ │ ├── agent-*/ # Agent adapters (claude-code, codex, aider)
│ │ ├── agent-*/ # Agent adapters (claude-code, codex, aider, acpx)
│ │ ├── workspace-*/ # Workspace providers (worktree, clone)
│ │ ├── tracker-*/ # Issue trackers (github, linear)
│ │ ├── scm-github/ # SCM adapter
Expand Down
12 changes: 12 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ Use this if:
- You need agent-specific configuration
- You're evaluating different AI coding assistants

### [acpx-pi.yaml](./acpx-pi.yaml)

**Using ACPX with the `pi` agent**

Shows how to route AO prompts through the built-in ACPX bridge while keeping AO in control of the session lifecycle.

Use this if:

- You want AO sessions to dispatch work through the `acpx` CLI
- You are starting with ACPX's `pi` agent
- You need a minimal example for `agentConfig.acpxAgent`

## Configuration Tips

1. **Start simple** - Use `simple-github.yaml` as a starting point
Expand Down
17 changes: 17 additions & 0 deletions examples/acpx-pi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ACPX worker setup using the built-in acpx agent plugin.

defaults:
runtime: tmux
agent: acpx
workspace: worktree
notifiers: [desktop]

projects:
my-app:
repo: owner/my-app
path: ~/code/my-app
defaultBranch: main
sessionPrefix: app
agentConfig:
acpxAgent: pi

4 changes: 4 additions & 0 deletions packages/cli/__tests__/lib/plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ describe("getAgentByName", () => {
expect(getAgentByName("aider").name).toBe("aider");
});

it("returns agent for acpx", () => {
expect(getAgentByName("acpx").name).toBe("acpx");
});

it("returns agent for opencode", () => {
expect(getAgentByName("opencode").name).toBe("opencode");
});
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dependencies": {
"@composio/ao-core": "workspace:*",
"@composio/ao-plugin-agent-aider": "workspace:*",
"@composio/ao-plugin-agent-acpx": "workspace:*",
"@composio/ao-plugin-agent-claude-code": "workspace:*",
"@composio/ao-plugin-agent-codex": "workspace:*",
"@composio/ao-plugin-agent-opencode": "workspace:*",
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/lib/config-instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ readyThresholdMs: 300000 # Ms before "ready" session becomes "idle" (defaul

defaults:
runtime: tmux # tmux | process
agent: claude-code # claude-code | aider | codex | opencode
agent: claude-code # claude-code | aider | codex | acpx | opencode
workspace: worktree # worktree | clone
notifiers: # List of active notifier plugins
- desktop # desktop | slack | webhook | composio | openclaw
Expand All @@ -46,8 +46,9 @@ projects:

# ── Agent configuration (optional) ────────────────────────────
agentConfig:
permissions: auto # auto | manual — agent permission mode
permissions: auto-edit # permissionless | default | auto-edit | suggest
model: claude-sonnet-4-20250514
# acpxAgent: pi # Required when agent: acpx (currently only "pi" is supported)

# ── Agent rules (optional) ────────────────────────────────────
agentRules: | # Inline rules passed to every agent prompt
Expand Down Expand Up @@ -117,7 +118,7 @@ notificationRouting:

# ── Available plugins ───────────────────────────────────────────────
#
# Agent: claude-code, aider, codex, opencode
# Agent: claude-code, aider, codex, acpx, opencode
# Runtime: tmux, process
# Workspace: worktree, clone
# SCM: github, gitlab
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/lib/detect-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const AGENT_PLUGINS: Array<{ name: string; pkg: string }> = [
{ name: "claude-code", pkg: "@composio/ao-plugin-agent-claude-code" },
{ name: "aider", pkg: "@composio/ao-plugin-agent-aider" },
{ name: "codex", pkg: "@composio/ao-plugin-agent-codex" },
{ name: "acpx", pkg: "@composio/ao-plugin-agent-acpx" },
{ name: "opencode", pkg: "@composio/ao-plugin-agent-opencode" },
];

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/lib/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Agent, OrchestratorConfig, SCM } from "@composio/ao-core";
import acpxPlugin from "@composio/ao-plugin-agent-acpx";
import claudeCodePlugin from "@composio/ao-plugin-agent-claude-code";
import codexPlugin from "@composio/ao-plugin-agent-codex";
import aiderPlugin from "@composio/ao-plugin-agent-aider";
import opencodePlugin from "@composio/ao-plugin-agent-opencode";
import githubSCMPlugin from "@composio/ao-plugin-scm-github";

const agentPlugins: Record<string, { create(): Agent }> = {
acpx: acpxPlugin,
"claude-code": claudeCodePlugin,
codex: codexPlugin,
aider: aiderPlugin,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Every interface the system uses is defined here. If you're working on any part o
**Main interfaces:**

- `Runtime` — where sessions execute (tmux, docker, k8s)
- `Agent` — AI coding tool adapter (claude-code, codex, aider)
- `Agent` — AI coding tool adapter (claude-code, codex, aider, acpx)
- `Workspace` — code isolation (worktree, clone)
- `Tracker` — issue tracking (GitHub Issues, Linear)
- `SCM` — PR/CI/reviews (GitHub, GitLab)
Expand Down Expand Up @@ -93,7 +93,7 @@ Loads plugins and provides access to them:
**Built-in plugins** (loaded by default):

- runtime-tmux, runtime-process
- agent-claude-code, agent-codex, agent-aider, agent-opencode
- agent-claude-code, agent-codex, agent-aider, agent-acpx, agent-opencode
- workspace-worktree, workspace-clone
- tracker-github, tracker-linear
- scm-github
Expand Down
46 changes: 46 additions & 0 deletions packages/core/src/__tests__/config-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,52 @@ describe("Config Validation - Session Prefix Uniqueness", () => {
});
});

describe("Config Validation - Agent Specific Config", () => {
it("accepts acpxAgent in project agentConfig", () => {
const acceptedAgents = ["pi", "codex", "claude", "gemini"] as const;

for (const acpxAgent of acceptedAgents) {
const config = validateConfig({
defaults: {
runtime: "tmux",
agent: "acpx",
workspace: "worktree",
notifiers: [],
},
projects: {
proj1: {
path: "/repos/acpx-project",
repo: "org/acpx-project",
defaultBranch: "main",
agentConfig: {
acpxAgent,
},
},
},
});

expect(config.projects.proj1.agentConfig?.acpxAgent).toBe(acpxAgent);
}
});

it("rejects unsupported acpxAgent values", () => {
expect(() =>
validateConfig({
projects: {
proj1: {
path: "/repos/acpx-project",
repo: "org/acpx-project",
defaultBranch: "main",
agentConfig: {
acpxAgent: "oracle",
},
},
},
}),
).toThrow();
});
});

describe("Config Validation - Session Prefix Regex", () => {
it("accepts valid session prefixes", () => {
const validPrefixes = ["int", "app", "my-app", "app_v2", "app123"];
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/__tests__/plugin-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,26 @@ describe("loadBuiltins", () => {

const fakeClaudeCode = makePlugin("agent", "claude-code");
const fakeCodex = makePlugin("agent", "codex");
const fakeAcpx = makePlugin("agent", "acpx");
const fakeOpenCode = makePlugin("agent", "opencode");

await registry.loadBuiltins(undefined, async (pkg: string) => {
if (pkg === "@composio/ao-plugin-agent-claude-code") return fakeClaudeCode;
if (pkg === "@composio/ao-plugin-agent-codex") return fakeCodex;
if (pkg === "@composio/ao-plugin-agent-acpx") return fakeAcpx;
if (pkg === "@composio/ao-plugin-agent-opencode") return fakeOpenCode;
throw new Error(`Not found: ${pkg}`);
});

const agents = registry.list("agent");
expect(agents).toContainEqual(expect.objectContaining({ name: "claude-code", slot: "agent" }));
expect(agents).toContainEqual(expect.objectContaining({ name: "codex", slot: "agent" }));
expect(agents).toContainEqual(expect.objectContaining({ name: "acpx", slot: "agent" }));
expect(agents).toContainEqual(expect.objectContaining({ name: "opencode", slot: "agent" }));

expect(registry.get("agent", "codex")).not.toBeNull();
expect(registry.get("agent", "claude-code")).not.toBeNull();
expect(registry.get("agent", "acpx")).not.toBeNull();
expect(registry.get("agent", "opencode")).not.toBeNull();
});

Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/__tests__/session-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,49 @@ describe("spawn", () => {
expect(mockRuntime.sendMessage).toHaveBeenCalled();
vi.useRealTimers();
});

it("forwards acpxAgent through project agentConfig to the selected agent", async () => {
const acpxAgent = {
...mockAgent,
name: "acpx",
};
const registryWithAcpx: PluginRegistry = {
...mockRegistry,
get: vi.fn().mockImplementation((slot: string) => {
if (slot === "runtime") return mockRuntime;
if (slot === "agent") return acpxAgent;
if (slot === "workspace") return mockWorkspace;
return null;
}),
};
const configWithAcpx: OrchestratorConfig = {
...config,
defaults: {
...config.defaults,
agent: "acpx",
},
projects: {
...config.projects,
"my-app": {
...config.projects["my-app"],
agentConfig: {
acpxAgent: "pi",
},
},
},
};

const sm = createSessionManager({ config: configWithAcpx, registry: registryWithAcpx });
await sm.spawn({ projectId: "my-app", prompt: "Dispatch through ACPX" });

expect(acpxAgent.getLaunchCommand).toHaveBeenCalledWith(
expect.objectContaining({
projectConfig: expect.objectContaining({
agentConfig: expect.objectContaining({ acpxAgent: "pi" }),
}),
}),
);
});
});

describe("list", () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const AgentSpecificConfigSchema = z
permissions: AgentPermissionSchema,
model: z.string().optional(),
orchestratorModel: z.string().optional(),
acpxAgent: z.enum(["pi", "codex", "claude", "gemini"]).optional(),
opencodeSessionId: z.string().optional(),
})
.passthrough();
Expand All @@ -111,6 +112,7 @@ const RoleAgentSpecificConfigSchema = z
.optional(),
model: z.string().optional(),
orchestratorModel: z.string().optional(),
acpxAgent: z.enum(["pi", "codex", "claude", "gemini"]).optional(),
opencodeSessionId: z.string().optional(),
})
.passthrough();
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugin-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const BUILTIN_PLUGINS: Array<{ slot: PluginSlot; name: string; pkg: string }> =
{ slot: "agent", name: "claude-code", pkg: "@composio/ao-plugin-agent-claude-code" },
{ slot: "agent", name: "codex", pkg: "@composio/ao-plugin-agent-codex" },
{ slot: "agent", name: "aider", pkg: "@composio/ao-plugin-agent-aider" },
{ slot: "agent", name: "acpx", pkg: "@composio/ao-plugin-agent-acpx" },
{ slot: "agent", name: "opencode", pkg: "@composio/ao-plugin-agent-opencode" },
// Workspaces
{ slot: "workspace", name: "worktree", pkg: "@composio/ao-plugin-workspace-worktree" },
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,12 @@ export interface AgentSpecificConfig {
[key: string]: unknown;
}

export type AcpxAgentName = "pi" | "codex" | "claude" | "gemini";

export interface AcpxAgentConfig extends AgentSpecificConfig {
acpxAgent?: AcpxAgentName;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent-specific field pollutes shared base type, creating redundancy

Low Severity

acpxAgent is added directly to the shared AgentSpecificConfig base type, which pollutes all agent configs with an ACPX-specific field. This also makes the new AcpxAgentConfig interface completely redundant — it extends AgentSpecificConfig and only re-declares acpxAgent, which is already present on the parent. The as AcpxAgentConfig | undefined cast in the plugin is therefore unnecessary. This is inconsistent with how OpenCodeAgentConfig handles opencodeSessionId, which correctly lives only on the subtype.

Additional Locations (1)
Fix in Cursor Fix in Web


export interface OpenCodeAgentConfig extends AgentSpecificConfig {
opencodeSessionId?: string;
}
Expand Down
44 changes: 44 additions & 0 deletions packages/plugins/agent-acpx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@composio/ao-plugin-agent-acpx",
"version": "0.2.0",
"description": "Agent plugin: ACPX bridge",
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/ComposioHQ/agent-orchestrator.git",
"directory": "packages/plugins/agent-acpx"
},
"homepage": "https://github.com/ComposioHQ/agent-orchestrator",
"bugs": {
"url": "https://github.com/ComposioHQ/agent-orchestrator/issues"
},
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"clean": "rm -rf dist"
},
"dependencies": {
"@composio/ao-core": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.2.3",
"typescript": "^5.7.0",
"vitest": "^3.0.0"
}
}
Loading