refactor(cli): simplify agent mode command control flow
Context from recent agent work - Prior commits focused on voice/background model gating, speech resolver typing, and timeline cursor loading. - This change targets a separate CLI command path to avoid overlap. What changed - Normalized the optional mode argument once (`mode?.trim()`) and reused it. - Removed duplicated `client.close()` calls and centralized cleanup in a `finally` block. - Typed the daemon client variable explicitly (`Awaited<ReturnType<typeof connectToDaemon>> | undefined`). - Kept user-facing behavior and error codes stable, including DAEMON_NOT_RUNNING and MODE_OPERATION_FAILED. Reasoning - The previous implementation duplicated lifecycle handling and relied on non-null assertions for mode values. - Centralized resource cleanup lowers leak risk and makes control flow easier to audit. - This is a behavior-preserving refactor aligned with the refactor skill contract. Verification - `npm run typecheck` - `npx tsx packages/cli/tests/10-agent-mode.test.ts` Accomplishments for next agent - `runModeCommand` now has a single cleanup path and clearer branching for list vs set mode. - Mode argument handling is explicit and trimmed before use. Challenges / follow-up - CLI output typing still requires `AnyCommandResult<any>` for mixed-shape commands due the current `withOutput` generic design. - If desired, a future refactor can redesign output typing to support per-branch result schemas without `any`.
This commit is contained in:
@@ -37,6 +37,8 @@ export interface AgentModeOptions extends CommandOptions {
|
|||||||
list?: boolean
|
list?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This command returns two different data shapes (set result vs mode list).
|
||||||
|
// Keep `any` here to match the existing output wrapper generic contract.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type AgentModeResult = AnyCommandResult<any>
|
export type AgentModeResult = AnyCommandResult<any>
|
||||||
|
|
||||||
@@ -46,10 +48,11 @@ export async function runModeCommand(
|
|||||||
options: AgentModeOptions,
|
options: AgentModeOptions,
|
||||||
_command: Command
|
_command: Command
|
||||||
): Promise<AgentModeResult> {
|
): Promise<AgentModeResult> {
|
||||||
|
const normalizedMode = mode?.trim()
|
||||||
const host = getDaemonHost({ host: options.host as string | undefined })
|
const host = getDaemonHost({ host: options.host as string | undefined })
|
||||||
|
|
||||||
// Validate arguments
|
// Validate arguments
|
||||||
if (!options.list && !mode) {
|
if (!options.list && !normalizedMode) {
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
code: 'MISSING_ARGUMENT',
|
code: 'MISSING_ARGUMENT',
|
||||||
message: 'Mode argument required unless --list is specified',
|
message: 'Mode argument required unless --list is specified',
|
||||||
@@ -58,20 +61,9 @@ export async function runModeCommand(
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
let client
|
let client: Awaited<ReturnType<typeof connectToDaemon>> | undefined
|
||||||
try {
|
try {
|
||||||
client = await connectToDaemon({ host: options.host as string | undefined })
|
client = await connectToDaemon({ host: options.host as string | undefined })
|
||||||
} catch (err) {
|
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
|
||||||
const error: CommandError = {
|
|
||||||
code: 'DAEMON_NOT_RUNNING',
|
|
||||||
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
||||||
details: 'Start the daemon with: paseo daemon start',
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const agent = await client.fetchAgent(id)
|
const agent = await client.fetchAgent(id)
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
@@ -87,8 +79,6 @@ export async function runModeCommand(
|
|||||||
// List available modes for this agent
|
// List available modes for this agent
|
||||||
const availableModes = agent.availableModes ?? []
|
const availableModes = agent.availableModes ?? []
|
||||||
|
|
||||||
await client.close()
|
|
||||||
|
|
||||||
const items: AgentMode[] = availableModes.map((m) => ({
|
const items: AgentMode[] = availableModes.map((m) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
label: m.label,
|
label: m.label,
|
||||||
@@ -101,31 +91,52 @@ export async function runModeCommand(
|
|||||||
schema: modeListSchema,
|
schema: modeListSchema,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set the agent mode
|
if (!normalizedMode) {
|
||||||
await client.setAgentMode(resolvedId, mode!)
|
const error: CommandError = {
|
||||||
|
code: 'MISSING_ARGUMENT',
|
||||||
|
message: 'Mode argument required unless --list is specified',
|
||||||
|
details: 'Usage: paseo agent mode <id> <mode> | paseo agent mode --list <id>',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
await client.close()
|
// Set the agent mode
|
||||||
|
await client.setAgentMode(resolvedId, normalizedMode)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'single',
|
type: 'single',
|
||||||
data: {
|
data: {
|
||||||
agentId: resolvedId.slice(0, 7),
|
agentId: resolvedId.slice(0, 7),
|
||||||
mode: mode!,
|
mode: normalizedMode,
|
||||||
},
|
},
|
||||||
schema: setModeSchema,
|
schema: setModeSchema,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.close().catch(() => {})
|
|
||||||
// Re-throw if it's already a CommandError
|
// Re-throw if it's already a CommandError
|
||||||
if (err && typeof err === 'object' && 'code' in err) {
|
if (err && typeof err === 'object' && 'code' in err) {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'DAEMON_NOT_RUNNING',
|
||||||
|
message: `Cannot connect to daemon at ${host}: ${message}`,
|
||||||
|
details: 'Start the daemon with: paseo daemon start',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
code: 'MODE_OPERATION_FAILED',
|
code: 'MODE_OPERATION_FAILED',
|
||||||
message: `Failed to ${options.list ? 'list modes' : 'set mode'}: ${message}`,
|
message: `Failed to ${options.list ? 'list modes' : 'set mode'}: ${message}`,
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
|
} finally {
|
||||||
|
if (client) {
|
||||||
|
await client.close().catch(() => {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user