mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(server): surface TRAE CLI slash commands and skills over ACP (#1896)
traecli publishes its slash commands and skills asynchronously via the standard ACP available_commands_update notification ~400ms after session/new resolves. Because the catalog entry runs through the plain GenericACPAgentClient (waitForInitialCommands defaults to false), listCommands() resolves before that first batch arrives and the Paseo UI shows an empty slash menu — intermittently, depending on whether the menu is opened inside the ~400ms race window. Add a thin TraeACPAgentClient that sets waitForInitialCommands: true (10s timeout) and wire it into the extends:"acp" derived-provider branch, mirroring CursorACPAgentClient exactly. Cursor has the identical async-commands behavior; this reuses that adopted pattern rather than introducing anything provider- specific. Unlike Kiro (#1792), traecli uses the standard available_commands_update update type, so no extensionCommandsParser is needed.
This commit is contained in:
committed by
GitHub
parent
5d25ed6bf8
commit
61df450366
@@ -341,7 +341,7 @@ const CATALOG_DATA = [
|
||||
description: "ByteDance's official TRAE coding agent with native ACP support",
|
||||
version: "manual",
|
||||
iconId: "traecli",
|
||||
installLink: "https://docs.trae.cn/cli",
|
||||
installLink: "https://docs.trae.cn/cli_get-started-with-trae-cli",
|
||||
command: ["traecli", "acp", "serve"],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,6 +25,11 @@ const mockState = vi.hoisted(() => {
|
||||
env?: Record<string, string>;
|
||||
providerParams?: unknown;
|
||||
}>,
|
||||
trae: [] as Array<{
|
||||
command: string[];
|
||||
env?: Record<string, string>;
|
||||
providerParams?: unknown;
|
||||
}>,
|
||||
pi: [] as ConstructorEntry[],
|
||||
genericAcp: [] as Array<{
|
||||
command: string[];
|
||||
@@ -41,6 +46,7 @@ const mockState = vi.hoisted(() => {
|
||||
this.constructorArgs.codex = [];
|
||||
this.constructorArgs.copilot = [];
|
||||
this.constructorArgs.cursor = [];
|
||||
this.constructorArgs.trae = [];
|
||||
this.constructorArgs.pi = [];
|
||||
this.constructorArgs.genericAcp = [];
|
||||
this.isCommandAvailable.mockReset();
|
||||
@@ -375,6 +381,59 @@ vi.mock("./providers/cursor-acp-agent.js", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./providers/trae-acp-agent.js", () => ({
|
||||
TraeACPAgentClient: class TraeACPAgentClient {
|
||||
readonly capabilities = {
|
||||
supportsStreaming: true,
|
||||
supportsSessionPersistence: true,
|
||||
supportsDynamicModes: true,
|
||||
supportsMcpServers: true,
|
||||
supportsReasoningStream: true,
|
||||
supportsToolInvocations: true,
|
||||
};
|
||||
readonly provider = "acp";
|
||||
readonly runtimeSettings?: unknown;
|
||||
|
||||
constructor(options: {
|
||||
command: string[];
|
||||
env?: Record<string, string>;
|
||||
providerParams?: unknown;
|
||||
}) {
|
||||
this.runtimeSettings = {
|
||||
command: {
|
||||
mode: "replace",
|
||||
argv: options.command,
|
||||
},
|
||||
env: options.env,
|
||||
};
|
||||
mockState.constructorArgs.trae.push({
|
||||
command: options.command,
|
||||
env: options.env,
|
||||
providerParams: options.providerParams,
|
||||
});
|
||||
}
|
||||
|
||||
async createSession(): Promise<never> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
async resumeSession(): Promise<never> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
async fetchCatalog(): Promise<ProviderCatalog> {
|
||||
return {
|
||||
models: mockState.runtimeModels.get(this.provider) ?? [],
|
||||
modes: [],
|
||||
};
|
||||
}
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
AGENT_PROVIDER_DEFINITIONS,
|
||||
buildProviderRegistry,
|
||||
@@ -638,6 +697,33 @@ test("cursor provider extending acp uses CursorACPAgentClient", () => {
|
||||
expect(mockState.constructorArgs.genericAcp).toEqual([]);
|
||||
});
|
||||
|
||||
test("traecli provider extending acp uses TraeACPAgentClient", () => {
|
||||
const registry = buildProviderRegistry(logger, {
|
||||
providerOverrides: {
|
||||
traecli: {
|
||||
extends: "acp",
|
||||
label: "TRAE CLI",
|
||||
command: ["traecli", "acp", "serve"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.traecli.createClient(logger).provider).toBe("traecli");
|
||||
expect(mockState.constructorArgs.trae).toEqual([
|
||||
{
|
||||
command: ["traecli", "acp", "serve"],
|
||||
env: undefined,
|
||||
providerParams: undefined,
|
||||
},
|
||||
{
|
||||
command: ["traecli", "acp", "serve"],
|
||||
env: undefined,
|
||||
providerParams: undefined,
|
||||
},
|
||||
]);
|
||||
expect(mockState.constructorArgs.genericAcp).toEqual([]);
|
||||
});
|
||||
|
||||
test('extends: "acp" without command throws', () => {
|
||||
expect(() =>
|
||||
buildProviderRegistry(logger, {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { GenericACPAgentClient } from "./providers/generic-acp-agent.js";
|
||||
import { KiroACPAgentClient } from "./providers/kiro-acp-agent.js";
|
||||
import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
|
||||
import { PiRpcAgentClient } from "./providers/pi/agent.js";
|
||||
import { TraeACPAgentClient } from "./providers/trae-acp-agent.js";
|
||||
import { MockLoadTestAgentClient } from "./providers/mock-load-test-agent.js";
|
||||
import { MockSlowProviderClient } from "./providers/mock-slow-provider.js";
|
||||
import {
|
||||
@@ -660,6 +661,9 @@ function addDerivedProviders(
|
||||
if (providerId === "kiro") {
|
||||
return new KiroACPAgentClient(acpOptions);
|
||||
}
|
||||
if (providerId === "traecli") {
|
||||
return new TraeACPAgentClient(acpOptions);
|
||||
}
|
||||
return new GenericACPAgentClient(acpOptions);
|
||||
},
|
||||
});
|
||||
|
||||
30
packages/server/src/server/agent/providers/trae-acp-agent.ts
Normal file
30
packages/server/src/server/agent/providers/trae-acp-agent.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { Logger } from "pino";
|
||||
|
||||
import { GenericACPAgentClient } from "./generic-acp-agent.js";
|
||||
|
||||
interface TraeACPAgentClientOptions {
|
||||
logger: Logger;
|
||||
command: [string, ...string[]];
|
||||
env?: Record<string, string>;
|
||||
providerId?: string;
|
||||
label?: string;
|
||||
providerParams?: unknown;
|
||||
}
|
||||
|
||||
const TRAE_INITIAL_COMMANDS_WAIT_TIMEOUT_MS = 10_000;
|
||||
|
||||
export class TraeACPAgentClient extends GenericACPAgentClient {
|
||||
constructor(options: TraeACPAgentClientOptions) {
|
||||
super({
|
||||
logger: options.logger,
|
||||
command: options.command,
|
||||
env: options.env,
|
||||
providerId: options.providerId,
|
||||
label: options.label,
|
||||
providerParams: options.providerParams,
|
||||
// traecli publishes slash commands and skills asynchronously via available_commands_update.
|
||||
waitForInitialCommands: true,
|
||||
initialCommandsWaitTimeoutMs: TRAE_INITIAL_COMMANDS_WAIT_TIMEOUT_MS,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ Pick any of these from the in-app provider catalog. Each entry is a one-click in
|
||||
- [Qwen Code](https://qwenlm.github.io/qwen-code-docs/en/users/overview), Alibaba's Qwen coding assistant.
|
||||
- [siGit Code](https://github.com/getsigit/sigit), local-first coding agent with optional on-device LLM.
|
||||
- [Stakpak](https://stakpak.dev/), Rust-based DevOps agent.
|
||||
- [TRAE CLI](https://docs.trae.cn/cli), ByteDance's official TRAE coding agent.
|
||||
- [TRAE CLI](https://docs.trae.cn/cli_get-started-with-trae-cli), ByteDance's official TRAE coding agent.
|
||||
- [VT Code](https://github.com/vinhnx/VTCode/blob/main/docs/guides/zed-acp.md), open-source multi-provider coding agent.
|
||||
|
||||
The in-app catalog is the canonical, version-pinned source. Anything not listed here can still be added manually, see [Custom providers](/docs/custom-providers).
|
||||
|
||||
Reference in New Issue
Block a user