* 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
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import { Command } from "commander";
|
|
import chalk from "chalk";
|
|
import { generateLocalPairingOffer, loadConfig, resolvePaseoHome } from "@getpaseo/server";
|
|
import { tryConnectToDaemon } from "../../utils/client.js";
|
|
import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
|
|
import { addJsonOption } from "../../utils/command-options.js";
|
|
|
|
interface PairOptions {
|
|
home?: string;
|
|
json?: boolean;
|
|
}
|
|
|
|
const PAIRING_DAEMON_RPC_TIMEOUT_MS = 1500;
|
|
|
|
export function pairCommand(): Command {
|
|
return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
|
|
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
|
|
.action(async (_options: PairOptions, command: Command) => {
|
|
await runPairCommand(command.optsWithGlobals());
|
|
});
|
|
}
|
|
|
|
export async function runPairCommand(options: PairOptions): Promise<void> {
|
|
if (options.home) {
|
|
process.env.PASEO_HOME = options.home;
|
|
}
|
|
|
|
const paseoHome = resolvePaseoHome();
|
|
const state = resolveLocalDaemonState({ home: paseoHome });
|
|
const host = resolveTcpHostFromListen(state.listen);
|
|
|
|
// Try to get the pairing offer from the running daemon first.
|
|
if (host) {
|
|
const client = await tryConnectToDaemon({ host, timeout: 1500 });
|
|
if (client) {
|
|
const supportsDaemonStatusRpc =
|
|
client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
|
|
if (supportsDaemonStatusRpc) {
|
|
try {
|
|
const offer = await client.getDaemonPairingOffer({
|
|
timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
|
|
});
|
|
await client.close().catch(() => {});
|
|
outputPairingResult(
|
|
{ relayEnabled: offer.relayEnabled, url: offer.url, qr: offer.qr ?? null },
|
|
options,
|
|
);
|
|
return;
|
|
} catch {
|
|
// COMPAT(daemon-rpc-rollout): fall back to CLI-side pairing generation while
|
|
// old daemons lack daemonStatusRpc. Remove once the daemon floor is past
|
|
// v0.1.76; pairing should come from daemon.get_pairing_offer.
|
|
}
|
|
}
|
|
await client.close().catch(() => {});
|
|
}
|
|
}
|
|
|
|
// Fall back to local pairing offer generation.
|
|
const config = loadConfig(paseoHome);
|
|
const pairing = await generateLocalPairingOffer({
|
|
paseoHome,
|
|
relayEnabled: config.relayEnabled,
|
|
relayEndpoint: config.relayEndpoint,
|
|
relayPublicEndpoint: config.relayPublicEndpoint,
|
|
relayUseTls: config.relayUseTls,
|
|
relayPublicUseTls: config.relayPublicUseTls,
|
|
appBaseUrl: config.appBaseUrl,
|
|
includeQr: true,
|
|
});
|
|
|
|
outputPairingResult(pairing, options);
|
|
}
|
|
|
|
function outputPairingResult(
|
|
pairing: { relayEnabled: boolean; url: string | null; qr: string | null },
|
|
options: PairOptions,
|
|
): void {
|
|
if (!pairing.relayEnabled || !pairing.url) {
|
|
console.error(chalk.red("Relay pairing is disabled for this daemon config."));
|
|
console.error(chalk.yellow("Enable relay and run this command again."));
|
|
process.exit(1);
|
|
}
|
|
|
|
if (options.json) {
|
|
process.stdout.write(
|
|
`${JSON.stringify(
|
|
{
|
|
relayEnabled: pairing.relayEnabled,
|
|
url: pairing.url,
|
|
qr: pairing.qr,
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const qrBlock = pairing.qr ? `${pairing.qr}\n` : "";
|
|
process.stdout.write(`\nScan to pair:\n${qrBlock}${pairing.url}\n`);
|
|
}
|