mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
* fix(client): wait longer for session responses * fix(client): default RPC waits to sixty seconds * fix(app): preserve detached stream scroll on delayed history Code drift: longer client RPC waits let delayed timeline responses arrive after a user scroll-away, so web stream anchoring must not reattach on transient scroll-top resets. Restore the 15s connect deadline and leave app initialization slack above the default 60s session RPC wait. * fix(client): keep helper waits within caller deadlines Review fix: the 60s default session RPC wait leaked into wait previews and waitForAgentUpsert helper fetches. Bound those helper RPCs to the caller deadline or a short best-effort preview timeout, and allow small scroll ranges to reattach at bottom. * fix(client): respect caller timeout budgets * fix(cli): keep diagnostic probes responsive * Refactor daemon client request options * Preserve daemon client legacy overloads
140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
import type { Command } from "commander";
|
|
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
import type {
|
|
CommandOptions,
|
|
OutputSchema,
|
|
CommandError,
|
|
AnyCommandResult,
|
|
} from "../../output/index.js";
|
|
import type { AgentMode } from "@getpaseo/protocol/agent-types";
|
|
|
|
/** Result for setting mode */
|
|
export interface SetModeResult {
|
|
agentId: string;
|
|
mode: string;
|
|
}
|
|
|
|
/** Schema for mode list output */
|
|
export const modeListSchema: OutputSchema<AgentMode> = {
|
|
idField: "id",
|
|
columns: [
|
|
{ header: "MODE", field: "id", width: 15 },
|
|
{ header: "LABEL", field: "label", width: 25 },
|
|
{ header: "DESCRIPTION", field: "description", width: 40 },
|
|
],
|
|
};
|
|
|
|
/** Schema for set mode output */
|
|
export const setModeSchema: OutputSchema<SetModeResult> = {
|
|
idField: "agentId",
|
|
columns: [
|
|
{ header: "AGENT ID", field: "agentId", width: 12 },
|
|
{ header: "MODE", field: "mode", width: 20 },
|
|
],
|
|
};
|
|
|
|
export interface AgentModeOptions extends CommandOptions {
|
|
list?: boolean;
|
|
}
|
|
|
|
const missingModeError = (): CommandError => ({
|
|
code: "MISSING_ARGUMENT",
|
|
message: "Mode argument required unless --list is specified",
|
|
details: "Usage: paseo agent mode <id> <mode> | paseo agent mode --list <id>",
|
|
});
|
|
|
|
// 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
|
|
export type AgentModeResult = AnyCommandResult<any>;
|
|
|
|
export async function runModeCommand(
|
|
id: string,
|
|
mode: string | undefined,
|
|
options: AgentModeOptions,
|
|
_command: Command,
|
|
): Promise<AgentModeResult> {
|
|
const normalizedMode = mode?.trim();
|
|
const host = getDaemonHost({ host: options.host });
|
|
|
|
// Validate arguments
|
|
if (!options.list && !normalizedMode) {
|
|
throw missingModeError();
|
|
}
|
|
|
|
let client: Awaited<ReturnType<typeof connectToDaemon>> | undefined;
|
|
try {
|
|
client = await connectToDaemon({ host: options.host });
|
|
const fetchResult = await client.fetchAgent({ agentId: id });
|
|
if (!fetchResult) {
|
|
const error: CommandError = {
|
|
code: "AGENT_NOT_FOUND",
|
|
message: `No agent found matching: ${id}`,
|
|
details: "Use `paseo ls` to list available agents",
|
|
};
|
|
throw error;
|
|
}
|
|
const agent = fetchResult.agent;
|
|
const resolvedId = agent.id;
|
|
|
|
if (options.list) {
|
|
// List available modes for this agent
|
|
const availableModes = agent.availableModes ?? [];
|
|
|
|
const items: AgentMode[] = availableModes.map((m) => ({
|
|
id: m.id,
|
|
label: m.label,
|
|
description: m.description,
|
|
}));
|
|
|
|
return {
|
|
type: "list",
|
|
data: items,
|
|
schema: modeListSchema,
|
|
};
|
|
}
|
|
|
|
if (!normalizedMode) {
|
|
throw missingModeError();
|
|
}
|
|
|
|
// Set the agent mode
|
|
await client.setAgentMode(resolvedId, normalizedMode);
|
|
|
|
return {
|
|
type: "single",
|
|
data: {
|
|
agentId: resolvedId.slice(0, 7),
|
|
mode: normalizedMode,
|
|
},
|
|
schema: setModeSchema,
|
|
};
|
|
} catch (err) {
|
|
// Re-throw if it's already a CommandError
|
|
if (err && typeof err === "object" && "code" in 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 error: CommandError = {
|
|
code: "MODE_OPERATION_FAILED",
|
|
message: `Failed to ${options.list ? "list modes" : "set mode"}: ${message}`,
|
|
};
|
|
throw error;
|
|
} finally {
|
|
if (client) {
|
|
await client.close().catch(() => {});
|
|
}
|
|
}
|
|
}
|