Fix commands not loading for Pi agent (#225)
This commit is contained in:
@@ -415,10 +415,39 @@ describe("transformPiModels", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ACPAgentSession slash commands", () => {
|
describe("ACPAgentSession slash commands", () => {
|
||||||
test("caches ACP available commands for listCommands", async () => {
|
test("returns immediately for ACP sessions that do not wait for async command discovery", async () => {
|
||||||
const session = createSession();
|
const session = createSession();
|
||||||
|
|
||||||
expect(await session.listCommands()).toEqual([]);
|
await expect(session.listCommands()).resolves.toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("waits for async available_commands_update when enabled", async () => {
|
||||||
|
const session = new ACPAgentSession(
|
||||||
|
{
|
||||||
|
provider: "pi",
|
||||||
|
cwd: "/tmp/paseo-acp-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "pi",
|
||||||
|
logger: createTestLogger(),
|
||||||
|
defaultCommand: ["pi-acp"],
|
||||||
|
defaultModes: [],
|
||||||
|
modelTransformer: transformPiModels,
|
||||||
|
sessionResponseTransformer: transformPiSessionResponse,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreaming: true,
|
||||||
|
supportsSessionPersistence: true,
|
||||||
|
supportsDynamicModes: true,
|
||||||
|
supportsMcpServers: false,
|
||||||
|
supportsReasoningStream: true,
|
||||||
|
supportsToolInvocations: true,
|
||||||
|
},
|
||||||
|
waitForInitialCommands: true,
|
||||||
|
initialCommandsWaitTimeoutMs: 1500,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const listCommandsPromise = session.listCommands();
|
||||||
|
|
||||||
(session as any).translateSessionUpdate({
|
(session as any).translateSessionUpdate({
|
||||||
sessionUpdate: "available_commands_update",
|
sessionUpdate: "available_commands_update",
|
||||||
@@ -434,6 +463,19 @@ describe("ACPAgentSession slash commands", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(await listCommandsPromise).toEqual([
|
||||||
|
{
|
||||||
|
name: "research_codebase",
|
||||||
|
description: "Search the workspace for relevant files",
|
||||||
|
argumentHint: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_plan",
|
||||||
|
description: "Draft a plan for the requested work",
|
||||||
|
argumentHint: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(await session.listCommands()).toEqual([
|
expect(await session.listCommands()).toEqual([
|
||||||
{
|
{
|
||||||
name: "research_codebase",
|
name: "research_codebase",
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ type ACPAgentClientOptions = {
|
|||||||
thinkingOptionId: string,
|
thinkingOptionId: string,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
capabilities?: AgentCapabilityFlags;
|
capabilities?: AgentCapabilityFlags;
|
||||||
|
waitForInitialCommands?: boolean;
|
||||||
|
initialCommandsWaitTimeoutMs?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ACPAgentSessionOptions = {
|
type ACPAgentSessionOptions = {
|
||||||
@@ -144,6 +146,8 @@ type ACPAgentSessionOptions = {
|
|||||||
capabilities: AgentCapabilityFlags;
|
capabilities: AgentCapabilityFlags;
|
||||||
handle?: AgentPersistenceHandle;
|
handle?: AgentPersistenceHandle;
|
||||||
launchEnv?: Record<string, string>;
|
launchEnv?: Record<string, string>;
|
||||||
|
waitForInitialCommands?: boolean;
|
||||||
|
initialCommandsWaitTimeoutMs?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SpawnedACPProcess = {
|
type SpawnedACPProcess = {
|
||||||
@@ -302,6 +306,8 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
sessionId: string,
|
sessionId: string,
|
||||||
thinkingOptionId: string,
|
thinkingOptionId: string,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
private readonly waitForInitialCommands: boolean;
|
||||||
|
private readonly initialCommandsWaitTimeoutMs: number;
|
||||||
|
|
||||||
constructor(options: ACPAgentClientOptions) {
|
constructor(options: ACPAgentClientOptions) {
|
||||||
this.provider = options.provider;
|
this.provider = options.provider;
|
||||||
@@ -314,6 +320,8 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
||||||
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
||||||
this.thinkingOptionWriter = options.thinkingOptionWriter;
|
this.thinkingOptionWriter = options.thinkingOptionWriter;
|
||||||
|
this.waitForInitialCommands = options.waitForInitialCommands ?? false;
|
||||||
|
this.initialCommandsWaitTimeoutMs = options.initialCommandsWaitTimeoutMs ?? 1500;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(
|
async createSession(
|
||||||
@@ -335,6 +343,8 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
thinkingOptionWriter: this.thinkingOptionWriter,
|
thinkingOptionWriter: this.thinkingOptionWriter,
|
||||||
capabilities: this.capabilities,
|
capabilities: this.capabilities,
|
||||||
launchEnv: launchContext?.env,
|
launchEnv: launchContext?.env,
|
||||||
|
waitForInitialCommands: this.waitForInitialCommands,
|
||||||
|
initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await session.initializeNewSession();
|
await session.initializeNewSession();
|
||||||
@@ -375,6 +385,8 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
capabilities: this.capabilities,
|
capabilities: this.capabilities,
|
||||||
handle,
|
handle,
|
||||||
launchEnv: launchContext?.env,
|
launchEnv: launchContext?.env,
|
||||||
|
waitForInitialCommands: this.waitForInitialCommands,
|
||||||
|
initialCommandsWaitTimeoutMs: this.initialCommandsWaitTimeoutMs,
|
||||||
});
|
});
|
||||||
await session.initializeResumedSession();
|
await session.initializeResumedSession();
|
||||||
return session;
|
return session;
|
||||||
@@ -617,6 +629,10 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
private lastActivityAt: string | null = null;
|
private lastActivityAt: string | null = null;
|
||||||
private configOptions: SessionConfigOption[] = [];
|
private configOptions: SessionConfigOption[] = [];
|
||||||
private cachedCommands: AgentSlashCommand[] = [];
|
private cachedCommands: AgentSlashCommand[] = [];
|
||||||
|
private commandsReadyDeferred: { promise: Promise<void>; resolve: () => void } | null = null;
|
||||||
|
private commandsReadySettled = false;
|
||||||
|
private waitForInitialCommands: boolean;
|
||||||
|
private initialCommandsWaitTimeoutMs: number;
|
||||||
private currentTurnUsage: AgentUsage | undefined;
|
private currentTurnUsage: AgentUsage | undefined;
|
||||||
private activeForegroundTurnId: string | null = null;
|
private activeForegroundTurnId: string | null = null;
|
||||||
private closed = false;
|
private closed = false;
|
||||||
@@ -645,6 +661,8 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
this.currentModel = config.model ?? null;
|
this.currentModel = config.model ?? null;
|
||||||
this.thinkingOptionId = config.thinkingOptionId ?? null;
|
this.thinkingOptionId = config.thinkingOptionId ?? null;
|
||||||
this.currentTitle = config.title ?? null;
|
this.currentTitle = config.title ?? null;
|
||||||
|
this.waitForInitialCommands = options.waitForInitialCommands ?? false;
|
||||||
|
this.initialCommandsWaitTimeoutMs = options.initialCommandsWaitTimeoutMs ?? 1500;
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): string | null {
|
get id(): string | null {
|
||||||
@@ -876,7 +894,59 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
return this.currentMode;
|
return this.currentMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureCommandsReadyDeferred(): void {
|
||||||
|
if (this.commandsReadyDeferred || this.commandsReadySettled || this.cachedCommands.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolve!: () => void;
|
||||||
|
const promise = new Promise<void>((r) => {
|
||||||
|
resolve = r;
|
||||||
|
});
|
||||||
|
this.commandsReadyDeferred = { promise, resolve };
|
||||||
|
}
|
||||||
|
|
||||||
|
private settleCommandsReady(): void {
|
||||||
|
if (this.commandsReadySettled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.commandsReadySettled = true;
|
||||||
|
this.commandsReadyDeferred?.resolve();
|
||||||
|
this.commandsReadyDeferred = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForCommandsReady(): Promise<void> {
|
||||||
|
const deferred = this.commandsReadyDeferred;
|
||||||
|
if (!deferred) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
try {
|
||||||
|
await Promise.race([
|
||||||
|
deferred.promise,
|
||||||
|
new Promise<void>((resolve) => {
|
||||||
|
timer = setTimeout(resolve, this.initialCommandsWaitTimeoutMs);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listCommands(): Promise<AgentSlashCommand[]> {
|
async listCommands(): Promise<AgentSlashCommand[]> {
|
||||||
|
if (this.cachedCommands.length > 0) {
|
||||||
|
return this.cachedCommands;
|
||||||
|
}
|
||||||
|
if (!this.waitForInitialCommands || this.closed) {
|
||||||
|
return this.cachedCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ensureCommandsReadyDeferred();
|
||||||
|
await this.waitForCommandsReady();
|
||||||
|
this.settleCommandsReady();
|
||||||
return this.cachedCommands;
|
return this.cachedCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,6 +1116,8 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
}
|
}
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
|
|
||||||
|
this.settleCommandsReady();
|
||||||
|
|
||||||
for (const pending of this.pendingPermissions.values()) {
|
for (const pending of this.pendingPermissions.values()) {
|
||||||
pending.resolve({ outcome: { outcome: "cancelled" } });
|
pending.resolve({ outcome: { outcome: "cancelled" } });
|
||||||
}
|
}
|
||||||
@@ -1392,6 +1464,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
description: command.description,
|
description: command.description,
|
||||||
argumentHint: "",
|
argumentHint: "",
|
||||||
}));
|
}));
|
||||||
|
this.settleCommandsReady();
|
||||||
return [];
|
return [];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -298,6 +298,8 @@ export class PiACPAgentClient extends ACPAgentClient {
|
|||||||
await connection.setSessionMode({ sessionId, modeId: thinkingOptionId });
|
await connection.setSessionMode({ sessionId, modeId: thinkingOptionId });
|
||||||
},
|
},
|
||||||
capabilities: PI_CAPABILITIES,
|
capabilities: PI_CAPABILITIES,
|
||||||
|
waitForInitialCommands: true,
|
||||||
|
initialCommandsWaitTimeoutMs: 1500,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user