Files
paseo/packages/server/src/poc-commands/commands-poc.test.ts
T
Mohamed BoudraandGitHub 9154f8fc4d fix(server): deduplicate workspaces by git worktree root (#190)
* fix(server): deduplicate workspaces by resolving to git worktree root

Agents running in subdirectories of the same git repo were creating
separate workspace entries in the sidebar. Now workspace IDs resolve
to the git worktree root (via the already-computed `worktreeRoot` from
`inspectCheckoutContext`), so all agents in the same checkout share a
single workspace. Stale subdirectory workspace records are archived
during reconciliation.

* fix(server): fix 3 broken test files in server unit suite

- relay-reconnect: move speech mock to correct constructor position and
  rename getSpeechReadiness→getReadiness after SpeechService refactor
- commands-poc: add credential/CLI availability guards matching
  claude-agent.integration.test.ts pattern
- claude-sdk-behavior: pass pathToClaudeCodeExecutable via findExecutable
  and add credential guards matching real provider configuration

* fix(server): fix race condition in loop-service test mock

Replace setTimeout(..., 0) with queueMicrotask() in ScriptedAgentSession
so turn_completed events fire before the verify check runs. Fixes flaky
CI failure where the file write hadn't completed before verification.

* fix(server): use /bin/sh for loop verify checks instead of /bin/zsh

More portable across CI environments. Also use queueMicrotask in test
mock to fix race condition between event emission and waiter setup.

* fix(server): correct import paths for isCommandAvailable and findExecutable

Import from utils/executable.js (where they're exported) instead of
provider-launch-config.js (which only imports them internally).
2026-04-04 15:35:41 +08:00

129 lines
4.5 KiB
TypeScript

/**
* TDD Test Suite for Command Support POC
*
* This tests the ability to:
* 1. Get available commands/skills from the Claude Agent SDK
* 2. Execute commands (determine if they're just prompts or something else)
*
* Key findings from SDK analysis:
* - `supportedCommands()` returns SlashCommand[] with name, description, argumentHint
* - Commands are executed by sending them as prompts with / prefix
* - The SDK init message includes `slash_commands: string[]` and `skills: string[]`
*
* IMPORTANT: Control methods like supportedCommands() work WITHOUT iterating the query first!
* Use an empty async generator for the prompt when you just need control methods.
* This pattern is used in claude-agent.ts listModels().
*/
import { describe, expect, test } from "vitest";
import {
query,
type SDKUserMessage,
} from "@anthropic-ai/claude-agent-sdk";
import { isCommandAvailable } from "../utils/executable.js";
const hasClaudeCredentials =
!!process.env.CLAUDE_CODE_OAUTH_TOKEN || !!process.env.ANTHROPIC_API_KEY;
const canRunClaudeIntegration = isCommandAvailable("claude") && hasClaudeCredentials;
// Pattern from claude-agent.ts listModels():
// Use an empty async generator when you just need control methods
function createEmptyPrompt(): AsyncGenerator<SDKUserMessage, void, undefined> {
return (async function* empty() {})();
}
describe("Claude Agent SDK Commands POC", () => {
describe("supportedCommands() API", () => {
test.runIf(canRunClaudeIntegration)("should return an array of SlashCommand objects", async () => {
// Use the pattern from claude-agent.ts:
// Create a query with empty prompt generator for control methods
const emptyPrompt = createEmptyPrompt();
const claudeQuery = query({
prompt: emptyPrompt,
options: {
cwd: process.cwd(),
permissionMode: "plan",
includePartialMessages: false,
settingSources: ["user", "project"], // Required to load skills
},
});
try {
// supportedCommands() is a control method - works without iterating
const commands = await claudeQuery.supportedCommands();
// Should be an array
expect(Array.isArray(commands)).toBe(true);
// Verify structure
if (commands.length > 0) {
const firstCommand = commands[0];
expect(typeof firstCommand.name).toBe("string");
expect(typeof firstCommand.description).toBe("string");
expect(typeof firstCommand.argumentHint).toBe("string");
expect(firstCommand.name.startsWith("/")).toBe(false);
}
} finally {
if (typeof claudeQuery.return === "function") {
try {
await claudeQuery.return();
} catch {
// ignore shutdown errors
}
}
}
}, 30000);
test.runIf(canRunClaudeIntegration)("should have valid SlashCommand structure for all commands", async () => {
const emptyPrompt = createEmptyPrompt();
const claudeQuery = query({
prompt: emptyPrompt,
options: {
cwd: process.cwd(),
permissionMode: "plan",
settingSources: ["user", "project"],
},
});
try {
const commands = await claudeQuery.supportedCommands();
expect(commands.length).toBeGreaterThan(0);
// Verify all commands have valid structure
for (const cmd of commands) {
expect(cmd).toHaveProperty("name");
expect(cmd).toHaveProperty("description");
expect(cmd).toHaveProperty("argumentHint");
expect(typeof cmd.name).toBe("string");
expect(typeof cmd.description).toBe("string");
expect(typeof cmd.argumentHint).toBe("string");
expect(cmd.name.length).toBeGreaterThan(0);
expect(cmd.name.startsWith("/")).toBe(false);
}
} finally {
await claudeQuery.return?.();
}
}, 30000);
});
describe("Command Execution", () => {
test("should explain that commands are prompts with / prefix", () => {
// This is a documentation test - commands ARE just prompts with / prefix
// To execute a command:
// 1. Create a user message with content: "/{commandName}"
// 2. Push it to the input stream
// 3. Iterate the query to receive responses
// The SDK handles command expansion:
// - Slash commands expand to their template content
// - Skills invoke the Skill tool
// - The model processes the expanded prompt like any other
expect(true).toBe(true); // Documentation-only test
});
});
});