mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(cli): query daemon for status and pairing offer over RPC
Closes #1081
This commit is contained in:
@@ -11,7 +11,7 @@ The namespace reads left to right:
|
||||
|
||||
- Domain: `checkout`
|
||||
- Provider or subsystem: `github`
|
||||
- Operation: `set_auto_merge`
|
||||
- Operation: `set_auto_merge`; this segment is a verb, not a noun. If you would name an RPC `noun.request`, name it `get_noun.request` instead.
|
||||
- Direction: `request` or `response`
|
||||
|
||||
Use dots, not slashes. Dots are protocol namespaces; slashes imply paths or transport routing.
|
||||
|
||||
@@ -392,9 +392,15 @@ export function resolveLocalDaemonState(options: { home?: string } = {}): LocalD
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...envWithHome(options.home),
|
||||
// Status should reflect local persisted config + pid file, not inherited daemon env overrides.
|
||||
// This is CLI-side defensive scrubbing; the daemon RPC is authoritative when available.
|
||||
PASEO_LISTEN: undefined,
|
||||
PASEO_HOSTNAMES: undefined,
|
||||
PASEO_ALLOWED_HOSTS: undefined,
|
||||
PASEO_RELAY_ENABLED: undefined,
|
||||
PASEO_RELAY_ENDPOINT: undefined,
|
||||
PASEO_RELAY_PUBLIC_ENDPOINT: undefined,
|
||||
PASEO_RELAY_USE_TLS: undefined,
|
||||
PASEO_RELAY_PUBLIC_USE_TLS: undefined,
|
||||
};
|
||||
const home = resolvePaseoHome(env);
|
||||
const config = loadConfig(home, { env });
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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 {
|
||||
@@ -22,6 +24,35 @@ export async function runPairCommand(options: PairOptions): Promise<void> {
|
||||
}
|
||||
|
||||
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();
|
||||
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,
|
||||
@@ -34,6 +65,13 @@ export async function runPairCommand(options: PairOptions): Promise<void> {
|
||||
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."));
|
||||
|
||||
@@ -10,6 +10,7 @@ interface ProviderBinaryStatus {
|
||||
label: string;
|
||||
path: string | null;
|
||||
version: string | null;
|
||||
source?: "daemon" | "local";
|
||||
}
|
||||
|
||||
interface DaemonStatus {
|
||||
@@ -99,7 +100,7 @@ function createStatusSchema(status: DaemonStatus): OutputSchema<StatusRow> {
|
||||
return "red";
|
||||
}
|
||||
if (item.key.startsWith(" ")) {
|
||||
if (item.value === "not found") return "red";
|
||||
if (item.value === "not found" || item.value === "not found (daemon)") return "red";
|
||||
if (item.value.endsWith("(--version failed)")) return "yellow";
|
||||
return "green";
|
||||
}
|
||||
@@ -149,7 +150,13 @@ function toStatusRows(status: DaemonStatus): StatusRow[] {
|
||||
rows.push({ key: "", value: "" });
|
||||
rows.push({ key: "Providers", value: "" });
|
||||
for (const provider of status.providers) {
|
||||
if (!provider.path) {
|
||||
if (provider.source === "daemon") {
|
||||
if (!provider.path) {
|
||||
rows.push({ key: ` ${provider.label}`, value: "not found (daemon)" });
|
||||
} else {
|
||||
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (daemon)` });
|
||||
}
|
||||
} else if (!provider.path) {
|
||||
rows.push({ key: ` ${provider.label}`, value: "not found" });
|
||||
} else if (!provider.version) {
|
||||
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (--version failed)` });
|
||||
@@ -210,6 +217,7 @@ interface DaemonProbeResult {
|
||||
runningAgents?: number;
|
||||
idleAgents?: number;
|
||||
daemonNodeOverride?: string;
|
||||
daemonProviders?: ProviderBinaryStatus[];
|
||||
note?: string;
|
||||
}
|
||||
|
||||
@@ -231,12 +239,32 @@ async function probeDaemonOverWebsocket(args: {
|
||||
}
|
||||
|
||||
const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
|
||||
const supportsDaemonStatusRpc =
|
||||
client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
|
||||
try {
|
||||
const agentsPayload = await client.fetchAgents({ filter: { includeArchived: true } });
|
||||
const agents = agentsPayload.entries.map((entry) => entry.agent);
|
||||
const runningAgents = agents.filter((a) => a.status === "running").length;
|
||||
const idleAgents = agents.filter((a) => a.status === "idle").length;
|
||||
|
||||
let daemonProviders: ProviderBinaryStatus[] | undefined;
|
||||
if (supportsDaemonStatusRpc) {
|
||||
try {
|
||||
const statusPayload = await client.getDaemonStatus();
|
||||
const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
|
||||
daemonProviders = statusPayload.providers.map((p) => ({
|
||||
label: labelMap.get(p.provider) ?? p.provider,
|
||||
path: p.available ? "available" : null,
|
||||
version: p.available ? null : (p.error ?? null),
|
||||
source: "daemon" as const,
|
||||
}));
|
||||
} catch {
|
||||
// COMPAT(daemon-rpc-rollout): fall back to CLI-side provider resolution while
|
||||
// old daemons lack daemonStatusRpc. Remove once the daemon floor is past
|
||||
// v0.1.76; status should come from daemon.get_status.
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.running) {
|
||||
return {
|
||||
connectedDaemon: "reachable",
|
||||
@@ -244,6 +272,7 @@ async function probeDaemonOverWebsocket(args: {
|
||||
runningAgents,
|
||||
idleAgents,
|
||||
daemonNodeOverride: "unknown (API reachable, PID unresolved)",
|
||||
daemonProviders,
|
||||
note: state.pidInfo
|
||||
? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
|
||||
: `Connected daemon is reachable at ${host} but no local daemon PID file was found`,
|
||||
@@ -255,6 +284,7 @@ async function probeDaemonOverWebsocket(args: {
|
||||
daemonVersion,
|
||||
runningAgents,
|
||||
idleAgents,
|
||||
daemonProviders,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
@@ -278,6 +308,7 @@ interface ProbeMergeState {
|
||||
daemonVersion: string | null;
|
||||
runningAgents: number | null;
|
||||
idleAgents: number | null;
|
||||
daemonProviders: ProviderBinaryStatus[] | undefined;
|
||||
note: string | undefined;
|
||||
}
|
||||
|
||||
@@ -290,6 +321,7 @@ function applyProbeToStatus(input: ProbeMergeState): Omit<ProbeMergeState, "prob
|
||||
daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
|
||||
runningAgents: probe.runningAgents !== undefined ? probe.runningAgents : input.runningAgents,
|
||||
idleAgents: probe.idleAgents !== undefined ? probe.idleAgents : input.idleAgents,
|
||||
daemonProviders: probe.daemonProviders ?? input.daemonProviders,
|
||||
note: probe.note ? appendNote(input.note, probe.note) : input.note,
|
||||
};
|
||||
}
|
||||
@@ -338,6 +370,7 @@ export async function runStatusCommand(
|
||||
let runningAgents: number | null = null;
|
||||
let idleAgents: number | null = null;
|
||||
let daemonVersion: string | null = null;
|
||||
let daemonProviders: ProviderBinaryStatus[] | undefined;
|
||||
let note: string | undefined;
|
||||
|
||||
if (!state.running && state.stalePidFile && state.pidInfo) {
|
||||
@@ -347,17 +380,26 @@ export async function runStatusCommand(
|
||||
|
||||
if (host) {
|
||||
const probe = await probeDaemonOverWebsocket({ host, state });
|
||||
({ connectedDaemon, localDaemon, daemonNode, daemonVersion, runningAgents, idleAgents, note } =
|
||||
applyProbeToStatus({
|
||||
probe,
|
||||
connectedDaemon,
|
||||
localDaemon,
|
||||
daemonNode,
|
||||
daemonVersion,
|
||||
runningAgents,
|
||||
idleAgents,
|
||||
note,
|
||||
}));
|
||||
({
|
||||
connectedDaemon,
|
||||
localDaemon,
|
||||
daemonNode,
|
||||
daemonVersion,
|
||||
runningAgents,
|
||||
idleAgents,
|
||||
daemonProviders,
|
||||
note,
|
||||
} = applyProbeToStatus({
|
||||
probe,
|
||||
connectedDaemon,
|
||||
localDaemon,
|
||||
daemonNode,
|
||||
daemonVersion,
|
||||
runningAgents,
|
||||
idleAgents,
|
||||
daemonProviders,
|
||||
note,
|
||||
}));
|
||||
} else {
|
||||
note = appendNote(note, "Daemon is configured for unix socket listen; API probe skipped");
|
||||
}
|
||||
@@ -370,7 +412,7 @@ export async function runStatusCommand(
|
||||
note = appendNote(note, serverIdResult.error);
|
||||
}
|
||||
|
||||
const providers = await checkProviderBinaries();
|
||||
const providers = daemonProviders ?? (await checkProviderBinaries());
|
||||
|
||||
const daemonStatus: DaemonStatus = {
|
||||
serverId,
|
||||
|
||||
@@ -60,6 +60,8 @@ import type {
|
||||
GetProvidersSnapshotResponseMessage,
|
||||
RefreshProvidersSnapshotResponseMessage,
|
||||
ProviderDiagnosticResponseMessage,
|
||||
DaemonGetStatusResponse,
|
||||
DaemonGetPairingOfferResponse,
|
||||
ListTerminalsResponse,
|
||||
CreateTerminalResponse,
|
||||
SubscribeTerminalResponse,
|
||||
@@ -317,6 +319,8 @@ type ListAvailableProvidersPayload = ListAvailableProvidersResponse["payload"];
|
||||
type GetProvidersSnapshotPayload = GetProvidersSnapshotResponseMessage["payload"];
|
||||
type RefreshProvidersSnapshotPayload = RefreshProvidersSnapshotResponseMessage["payload"];
|
||||
type ProviderDiagnosticPayload = ProviderDiagnosticResponseMessage["payload"];
|
||||
type DaemonStatusPayload = DaemonGetStatusResponse["payload"];
|
||||
type DaemonPairingOfferPayload = DaemonGetPairingOfferResponse["payload"];
|
||||
type ReadProjectConfigPayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "read_project_config_response" }
|
||||
@@ -3298,6 +3302,28 @@ export class DaemonClient {
|
||||
});
|
||||
}
|
||||
|
||||
async getDaemonStatus(requestId?: string): Promise<DaemonStatusPayload> {
|
||||
return this.sendCorrelatedSessionRequest({
|
||||
requestId,
|
||||
message: {
|
||||
type: "daemon.get_status.request",
|
||||
},
|
||||
responseType: "daemon.get_status.response",
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
async getDaemonPairingOffer(requestId?: string): Promise<DaemonPairingOfferPayload> {
|
||||
return this.sendCorrelatedSessionRequest({
|
||||
requestId,
|
||||
message: {
|
||||
type: "daemon.get_pairing_offer.request",
|
||||
},
|
||||
responseType: "daemon.get_pairing_offer.response",
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
async patchDaemonConfig(
|
||||
config: MutableDaemonConfigPatch,
|
||||
requestId?: string,
|
||||
|
||||
@@ -936,6 +936,16 @@ export async function createPaseoDaemon(
|
||||
workspaceGitService,
|
||||
github,
|
||||
config.pushNotificationSender,
|
||||
{
|
||||
listen: formatListenTarget(boundListenTarget ?? listenTarget),
|
||||
relay: {
|
||||
enabled: relayEnabled,
|
||||
endpoint: relayEndpoint,
|
||||
publicEndpoint: relayPublicEndpoint,
|
||||
useTls: relayUseTls,
|
||||
publicUseTls: relayPublicUseTls,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (relayEnabled) {
|
||||
|
||||
@@ -47,6 +47,8 @@ import type { TurnDetectionProvider } from "./speech/turn-detection-provider.js"
|
||||
import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
|
||||
import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
|
||||
import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
|
||||
import { getPidLockInfo } from "./pid-lock.js";
|
||||
import { generateLocalPairingOffer } from "./pairing-offer.js";
|
||||
import {
|
||||
DictationStreamManager,
|
||||
type DictationStreamOutboundMessage,
|
||||
@@ -595,6 +597,18 @@ export interface SessionOptions {
|
||||
agentProviderRuntimeSettings?: AgentProviderRuntimeSettingsMap;
|
||||
providerOverrides?: Record<string, ProviderOverride>;
|
||||
isDev?: boolean;
|
||||
serverId?: string;
|
||||
daemonVersion?: string;
|
||||
daemonRuntimeConfig?: {
|
||||
listen: string | null;
|
||||
relay: {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
publicEndpoint: string;
|
||||
useTls: boolean;
|
||||
publicUseTls: boolean;
|
||||
} | null;
|
||||
};
|
||||
}
|
||||
|
||||
export type SessionLifecycleIntent =
|
||||
@@ -805,6 +819,9 @@ export class Session {
|
||||
private readonly agentProviderRuntimeSettings: AgentProviderRuntimeSettingsMap | undefined;
|
||||
private readonly providerOverrides: Record<string, ProviderOverride> | undefined;
|
||||
private readonly isDev: boolean;
|
||||
private readonly serverId: string | undefined;
|
||||
private readonly daemonVersion: string | undefined;
|
||||
private readonly daemonRuntimeConfig: SessionOptions["daemonRuntimeConfig"];
|
||||
private voiceModeAgentId: string | null = null;
|
||||
private voiceModeBaseConfig: VoiceModeBaseConfig | null = null;
|
||||
|
||||
@@ -850,6 +867,9 @@ export class Session {
|
||||
agentProviderRuntimeSettings,
|
||||
providerOverrides,
|
||||
isDev,
|
||||
serverId,
|
||||
daemonVersion,
|
||||
daemonRuntimeConfig,
|
||||
} = options;
|
||||
this.clientId = clientId;
|
||||
this.appVersion = appVersion ?? null;
|
||||
@@ -901,6 +921,9 @@ export class Session {
|
||||
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
|
||||
this.providerOverrides = providerOverrides;
|
||||
this.isDev = isDev === true;
|
||||
this.serverId = serverId;
|
||||
this.daemonVersion = daemonVersion;
|
||||
this.daemonRuntimeConfig = daemonRuntimeConfig;
|
||||
this.abortController = new AbortController();
|
||||
this.workspaceDirectory = new WorkspaceDirectory({
|
||||
logger: this.sessionLogger,
|
||||
@@ -1864,6 +1887,10 @@ export class Session {
|
||||
payload: { requestId: msg.requestId, config: this.daemonConfigStore.get() },
|
||||
});
|
||||
return undefined;
|
||||
case "daemon.get_status.request":
|
||||
return this.handleDaemonGetStatusRequest(msg);
|
||||
case "daemon.get_pairing_offer.request":
|
||||
return this.handleDaemonGetPairingOfferRequest(msg);
|
||||
case "set_daemon_config_request":
|
||||
this.emit({
|
||||
type: "set_daemon_config_response",
|
||||
@@ -3813,6 +3840,86 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDaemonGetStatusRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "daemon.get_status.request" }>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const pidInfo = await getPidLockInfo(this.paseoHome);
|
||||
const providers = (await this.agentManager.listProviderAvailability()).map((p) => ({
|
||||
provider: p.provider,
|
||||
available: p.available,
|
||||
error: p.error ?? null,
|
||||
}));
|
||||
this.emit({
|
||||
type: "daemon.get_status.response",
|
||||
payload: {
|
||||
requestId: msg.requestId,
|
||||
serverId: this.serverId ?? "",
|
||||
version: this.daemonVersion ?? null,
|
||||
pid: process.pid,
|
||||
nodePath: process.execPath,
|
||||
startedAt: pidInfo?.startedAt ?? null,
|
||||
listen: this.daemonRuntimeConfig?.listen ?? null,
|
||||
relay: this.daemonRuntimeConfig?.relay ?? null,
|
||||
providers,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.sessionLogger.error({ err: error }, "Failed to handle daemon status request");
|
||||
this.emit({
|
||||
type: "daemon.get_status.response",
|
||||
payload: {
|
||||
requestId: msg.requestId,
|
||||
serverId: this.serverId ?? "",
|
||||
version: this.daemonVersion ?? null,
|
||||
pid: process.pid,
|
||||
nodePath: process.execPath,
|
||||
startedAt: null,
|
||||
listen: null,
|
||||
relay: null,
|
||||
providers: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDaemonGetPairingOfferRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "daemon.get_pairing_offer.request" }>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const relay = this.daemonRuntimeConfig?.relay;
|
||||
const pairing = await generateLocalPairingOffer({
|
||||
paseoHome: this.paseoHome,
|
||||
relayEnabled: relay?.enabled ?? true,
|
||||
relayEndpoint: relay?.endpoint,
|
||||
relayPublicEndpoint: relay?.publicEndpoint,
|
||||
relayUseTls: relay?.useTls,
|
||||
relayPublicUseTls: relay?.publicUseTls,
|
||||
includeQr: true,
|
||||
logger: this.sessionLogger,
|
||||
});
|
||||
this.emit({
|
||||
type: "daemon.get_pairing_offer.response",
|
||||
payload: {
|
||||
requestId: msg.requestId,
|
||||
url: pairing.url ?? "",
|
||||
qr: pairing.qr ?? null,
|
||||
relayEnabled: pairing.relayEnabled,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.sessionLogger.error({ err: error }, "Failed to handle daemon pairing offer request");
|
||||
this.emit({
|
||||
type: "rpc_error",
|
||||
payload: {
|
||||
requestId: msg.requestId,
|
||||
requestType: "daemon.get_pairing_offer.request",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleListAvailableProvidersRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "list_available_providers_request" }>,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -332,6 +332,18 @@ export class VoiceAssistantWebSocketServer {
|
||||
private readonly externalSessionsByKey: Map<string, SessionConnection> = new Map();
|
||||
private readonly serverId: string;
|
||||
private readonly daemonVersion: string;
|
||||
private readonly daemonRuntimeConfig:
|
||||
| {
|
||||
listen: string | null;
|
||||
relay: {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
publicEndpoint: string;
|
||||
useTls: boolean;
|
||||
publicUseTls: boolean;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
private readonly agentManager: AgentManager;
|
||||
private readonly agentStorage: AgentStorage;
|
||||
private readonly projectRegistry: ProjectRegistry;
|
||||
@@ -416,6 +428,16 @@ export class VoiceAssistantWebSocketServer {
|
||||
workspaceGitService?: WorkspaceGitService,
|
||||
github?: GitHubService,
|
||||
pushNotificationSender?: PushNotificationSender,
|
||||
daemonRuntimeConfig?: {
|
||||
listen: string | null;
|
||||
relay: {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
publicEndpoint: string;
|
||||
useTls: boolean;
|
||||
publicUseTls: boolean;
|
||||
};
|
||||
},
|
||||
) {
|
||||
this.logger = logger.child({ module: "websocket-server" });
|
||||
this.serverId = serverId;
|
||||
@@ -423,6 +445,7 @@ export class VoiceAssistantWebSocketServer {
|
||||
throw new MissingDaemonVersionError();
|
||||
}
|
||||
this.daemonVersion = daemonVersion.trim();
|
||||
this.daemonRuntimeConfig = daemonRuntimeConfig;
|
||||
this.agentManager = agentManager;
|
||||
this.agentStorage = agentStorage;
|
||||
this.projectRegistry = projectRegistry ?? createNoopProjectRegistry();
|
||||
@@ -921,6 +944,9 @@ export class VoiceAssistantWebSocketServer {
|
||||
agentProviderRuntimeSettings: this.agentProviderRuntimeSettings,
|
||||
providerOverrides: this.providerOverrides,
|
||||
isDev: this.isDev,
|
||||
serverId: this.serverId,
|
||||
daemonVersion: this.daemonVersion,
|
||||
daemonRuntimeConfig: this.daemonRuntimeConfig,
|
||||
});
|
||||
|
||||
connection = {
|
||||
@@ -1053,6 +1079,8 @@ export class VoiceAssistantWebSocketServer {
|
||||
providersSnapshot: true,
|
||||
// COMPAT(checkoutGithubSetAutoMerge): added in v0.1.75, remove gate after 2026-11-13.
|
||||
checkoutGithubSetAutoMerge: true,
|
||||
// COMPAT(daemonStatusRpc): added in v0.1.76, remove gate after 2026-11-18.
|
||||
daemonStatusRpc: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -979,6 +979,16 @@ export const WaitForFinishRequestSchema = z.object({
|
||||
timeoutMs: z.number().int().positive().optional(),
|
||||
});
|
||||
|
||||
export const DaemonGetStatusRequestSchema = z.object({
|
||||
type: z.literal("daemon.get_status.request"),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const DaemonGetPairingOfferRequestSchema = z.object({
|
||||
type: z.literal("daemon.get_pairing_offer.request"),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const GetDaemonConfigRequestMessageSchema = z.object({
|
||||
type: z.literal("get_daemon_config_request"),
|
||||
requestId: z.string(),
|
||||
@@ -1764,6 +1774,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
SetVoiceModeMessageSchema,
|
||||
SendAgentMessageRequestSchema,
|
||||
WaitForFinishRequestSchema,
|
||||
DaemonGetStatusRequestSchema,
|
||||
DaemonGetPairingOfferRequestSchema,
|
||||
GetDaemonConfigRequestMessageSchema,
|
||||
SetDaemonConfigRequestMessageSchema,
|
||||
ReadProjectConfigRequestMessageSchema,
|
||||
@@ -2026,6 +2038,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
.object({
|
||||
providersSnapshot: z.boolean().optional(),
|
||||
checkoutGithubSetAutoMerge: z.boolean().optional(),
|
||||
// COMPAT(daemonStatusRpc): added in v0.1.76, remove gate after 2026-11-18.
|
||||
daemonStatusRpc: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
@@ -2586,6 +2600,50 @@ export const GetDaemonConfigResponseMessageSchema = z.object({
|
||||
.passthrough(),
|
||||
});
|
||||
|
||||
export const DaemonGetStatusResponseSchema = z.object({
|
||||
type: z.literal("daemon.get_status.response"),
|
||||
payload: z
|
||||
.object({
|
||||
requestId: z.string(),
|
||||
serverId: z.string(),
|
||||
version: z.string().nullable().optional(),
|
||||
pid: z.number(),
|
||||
nodePath: z.string(),
|
||||
startedAt: z.string().nullable().optional(),
|
||||
listen: z.string().nullable(),
|
||||
relay: z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
endpoint: z.string(),
|
||||
publicEndpoint: z.string(),
|
||||
useTls: z.boolean(),
|
||||
publicUseTls: z.boolean(),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
providers: z.array(
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
available: z.boolean(),
|
||||
error: z.string().nullable().optional(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.passthrough(),
|
||||
});
|
||||
|
||||
export const DaemonGetPairingOfferResponseSchema = z.object({
|
||||
type: z.literal("daemon.get_pairing_offer.response"),
|
||||
payload: z
|
||||
.object({
|
||||
requestId: z.string(),
|
||||
url: z.string(),
|
||||
qr: z.string().nullable().optional(),
|
||||
relayEnabled: z.boolean(),
|
||||
})
|
||||
.passthrough(),
|
||||
});
|
||||
|
||||
export const SetDaemonConfigResponseMessageSchema = z.object({
|
||||
type: z.literal("set_daemon_config_response"),
|
||||
payload: z
|
||||
@@ -3481,6 +3539,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ClearAgentAttentionResponseMessageSchema,
|
||||
SendAgentMessageResponseMessageSchema,
|
||||
SetVoiceModeResponseMessageSchema,
|
||||
DaemonGetStatusResponseSchema,
|
||||
DaemonGetPairingOfferResponseSchema,
|
||||
GetDaemonConfigResponseMessageSchema,
|
||||
SetDaemonConfigResponseMessageSchema,
|
||||
ReadProjectConfigResponseMessageSchema,
|
||||
@@ -3643,6 +3703,8 @@ export type ListProviderFeaturesResponseMessage = z.infer<
|
||||
typeof ListProviderFeaturesResponseMessageSchema
|
||||
>;
|
||||
export type ListAvailableProvidersResponse = z.infer<typeof ListAvailableProvidersResponseSchema>;
|
||||
export type DaemonGetStatusResponse = z.infer<typeof DaemonGetStatusResponseSchema>;
|
||||
export type DaemonGetPairingOfferResponse = z.infer<typeof DaemonGetPairingOfferResponseSchema>;
|
||||
export type GetProvidersSnapshotResponseMessage = z.infer<
|
||||
typeof GetProvidersSnapshotResponseMessageSchema
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user