mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 23:23:21 +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
138 lines
3.9 KiB
TypeScript
138 lines
3.9 KiB
TypeScript
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
import { parseDuration } from "../../utils/duration.js";
|
|
import type { CommandError, CommandOptions } from "../../output/index.js";
|
|
import type { ChatMessageRow } from "./schema.js";
|
|
|
|
export interface ChatCommandOptions extends CommandOptions {
|
|
host?: string;
|
|
}
|
|
|
|
export async function connectChatClient(host?: string) {
|
|
const daemonHost = getDaemonHost({ host });
|
|
try {
|
|
const client = await connectToDaemon({ host });
|
|
return { client, daemonHost };
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
const error: CommandError = {
|
|
code: "DAEMON_NOT_RUNNING",
|
|
message: `Cannot connect to daemon at ${daemonHost}: ${message}`,
|
|
details: "Start the daemon with: paseo daemon start",
|
|
};
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function attachAgentNamesToMessages(
|
|
client: Awaited<ReturnType<typeof connectToDaemon>>,
|
|
messages: ChatMessageRow[],
|
|
options: { timeout?: number; bestEffort?: boolean } = {},
|
|
): Promise<ChatMessageRow[]> {
|
|
const agentIds = new Set<string>();
|
|
for (const message of messages) {
|
|
agentIds.add(message.author);
|
|
for (const mentionId of message.mentionAgentIds) {
|
|
agentIds.add(mentionId);
|
|
}
|
|
}
|
|
|
|
if (agentIds.size === 0) {
|
|
return messages;
|
|
}
|
|
|
|
let payload: Awaited<ReturnType<typeof client.fetchAgents>>;
|
|
try {
|
|
payload = await client.fetchAgents({
|
|
filter: { includeArchived: true },
|
|
...(typeof options.timeout === "number" ? { timeout: options.timeout } : {}),
|
|
});
|
|
} catch (error) {
|
|
if (options.bestEffort) {
|
|
return messages;
|
|
}
|
|
throw error;
|
|
}
|
|
const agentNames = new Map<string, string>();
|
|
for (const entry of payload.entries) {
|
|
const title = entry.agent.title?.trim();
|
|
if (title) {
|
|
agentNames.set(entry.agent.id, title);
|
|
}
|
|
}
|
|
|
|
return messages.map((message) => ({
|
|
...message,
|
|
authorName: agentNames.get(message.author) ?? null,
|
|
mentionLabels: message.mentionAgentIds.map((agentId) => {
|
|
const name = agentNames.get(agentId);
|
|
return name ? `${name} (${agentId})` : agentId;
|
|
}),
|
|
}));
|
|
}
|
|
|
|
export function toChatCommandError(code: string, action: string, err: unknown): CommandError {
|
|
if (err && typeof err === "object" && "code" in err && "message" in err) {
|
|
return err as CommandError;
|
|
}
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
const rpcCode =
|
|
typeof err === "object" && err !== null && "code" in err && typeof err.code === "string"
|
|
? err.code
|
|
: undefined;
|
|
|
|
return {
|
|
code: rpcCode ?? code,
|
|
message: `Failed to ${action}: ${message}`,
|
|
};
|
|
}
|
|
|
|
export function parseSinceValue(input?: string): string | undefined {
|
|
if (!input) {
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
const durationMs = parseDuration(input);
|
|
return new Date(Date.now() - durationMs).toISOString();
|
|
} catch {
|
|
const timestamp = new Date(input);
|
|
if (Number.isNaN(timestamp.getTime())) {
|
|
const error: CommandError = {
|
|
code: "INVALID_SINCE",
|
|
message: "Invalid --since value",
|
|
details: "Use a duration like 10m or an ISO timestamp.",
|
|
};
|
|
throw error;
|
|
}
|
|
return timestamp.toISOString();
|
|
}
|
|
}
|
|
|
|
export function parseTimeoutMs(input?: string): number | undefined {
|
|
if (!input) {
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
const timeoutMs = parseDuration(input);
|
|
if (timeoutMs <= 0) {
|
|
throw new Error("Timeout must be positive");
|
|
}
|
|
return timeoutMs;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
const error: CommandError = {
|
|
code: "INVALID_TIMEOUT",
|
|
message: "Invalid timeout value",
|
|
details: message,
|
|
};
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function resolveChatAuthorAgentId(): string {
|
|
const agentId = process.env.PASEO_AGENT_ID?.trim();
|
|
return agentId && agentId.length > 0 ? agentId : "manual";
|
|
}
|