mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Connect your Paseo daemon to Hub (#2035)
* feat(hub): connect daemons to Paseo Hub Make Hub an explicit daemon-owned relationship with local-only management and scoped access to Hub-owned executions. * fix(hub): harden relationship boundaries * fix(hub): harden relationship lifecycle * fix(hub): isolate CLI test entrypoint * fix(hub): run CLI tests from workspace source * fix(hub): settle failed relationship connections * fix(hub): resume interrupted owned turns Provider session rehydration does not continue foreground work lost during daemon shutdown. Persist narrowly scoped Hub execution intent and replay only an interrupted running initial turn. * fix(hub): harden relationship lifecycle Keep optional Hub authority from blocking daemon startup, revoke ambiguous enrollments durably, and close owned agents when their relationship no longer exists. Reject remote CLI connect targets before transmitting enrollment authority. * fix(hub): stop replaying interrupted turns Daemon restart cannot safely guarantee prompt idempotency across providers. Persist the normal closed session state while retaining Hub relationship, execution, and agent identity. * fix(hub): fail creates when prompts cannot start * fix: make Hub lifecycle cleanup deterministic * fix(hub): preserve fresh enrollment authority * fix(hub): contain enrollment retry failures * fix(hub): reject invalid socket transport URLs * fix(hub): bind socket transport to Hub authority * fix(hub): close relationship lifecycle gaps * test(hub): stabilize lifecycle coverage on Windows * fix(hub): close execution authority races * fix(hub): return relationship command errors * fix(app): preserve workspace navigation compatibility * fix(hub): correct relationship trust boundaries Authenticated daemon sessions own relationship management regardless of transport. The separate Hub session remains operation-allowlisted, rejects malformed execution inputs, and uses bounded outbound handshakes. * refactor(hub): authorize execution through sessions * fix(hub): validate persisted origins * fix(hub): retain local execution grants * fix(hub): enforce session scope boundaries Make session authority explicit and mutable without adding scope negotiation. Fence persisted Hub scopes and retire in-flight execution authority during cleanup and re-enrollment. * fix(server): preserve main session compatibility
This commit is contained in:
@@ -9,6 +9,7 @@ import { createScheduleCommand } from "./commands/schedule/index.js";
|
||||
import { createSpeechCommand } from "./commands/speech/index.js";
|
||||
import { createTerminalCommand } from "./commands/terminal/index.js";
|
||||
import { createWorktreeCommand } from "./commands/worktree/index.js";
|
||||
import { createHubCommand } from "./commands/hub/index.js";
|
||||
import { createHooksCommand } from "./commands/hooks.js";
|
||||
import { startCommand as daemonStartCommand } from "./commands/daemon/start.js";
|
||||
import { runStatusCommand as runDaemonStatusCommand } from "./commands/daemon/status.js";
|
||||
@@ -160,6 +161,7 @@ export function createCli(): Command {
|
||||
|
||||
// Daemon commands
|
||||
program.addCommand(createDaemonCommand());
|
||||
program.addCommand(createHubCommand());
|
||||
|
||||
// Chat commands
|
||||
program.addCommand(createChatCommand());
|
||||
|
||||
104
packages/cli/src/commands/hub/index.ts
Normal file
104
packages/cli/src/commands/hub/index.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Command } from "commander";
|
||||
import { withOutput, type ListResult, type OutputSchema } from "../../output/index.js";
|
||||
import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
|
||||
import { connectToDaemon } from "../../utils/client.js";
|
||||
|
||||
interface HubRow {
|
||||
state: string;
|
||||
daemonId: string | null;
|
||||
hub: string | null;
|
||||
scopes: string;
|
||||
connectedAt: string | null;
|
||||
error: string | null;
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
const schema: OutputSchema<HubRow> = {
|
||||
idField: "state",
|
||||
columns: [
|
||||
{ header: "STATE", field: "state" },
|
||||
{ header: "HUB", field: "hub" },
|
||||
{ header: "DAEMON", field: "daemonId" },
|
||||
{ header: "SCOPES", field: "scopes" },
|
||||
{ header: "CONNECTED", field: "connectedAt" },
|
||||
{ header: "ERROR", field: "error" },
|
||||
{ header: "WARNING", field: "warning" },
|
||||
],
|
||||
};
|
||||
|
||||
function result(
|
||||
status: {
|
||||
state: string;
|
||||
daemonId: string | null;
|
||||
hubOrigin: string | null;
|
||||
scopes: string[];
|
||||
connectedAt: string | null;
|
||||
lastError: string | null;
|
||||
},
|
||||
warning?: string,
|
||||
): ListResult<HubRow> {
|
||||
return {
|
||||
type: "list",
|
||||
data: [
|
||||
{
|
||||
state: status.state,
|
||||
daemonId: status.daemonId,
|
||||
hub: status.hubOrigin,
|
||||
scopes: status.scopes.join(", "),
|
||||
connectedAt: status.connectedAt,
|
||||
error: status.lastError,
|
||||
warning,
|
||||
},
|
||||
],
|
||||
schema,
|
||||
};
|
||||
}
|
||||
|
||||
async function withClient<T>(
|
||||
host: string | undefined,
|
||||
action: (client: Awaited<ReturnType<typeof connectToDaemon>>) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const client = await connectToDaemon({ host });
|
||||
try {
|
||||
return await action(client);
|
||||
} finally {
|
||||
await client.close().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export function createHubCommand(): Command {
|
||||
const hub = new Command("hub").description("Manage this daemon's Paseo Hub relationship");
|
||||
addJsonAndDaemonHostOptions(
|
||||
hub.command("connect").argument("<url>").requiredOption("--token <token>"),
|
||||
).action(
|
||||
withOutput(async (...args) => {
|
||||
const url = args[0] as string;
|
||||
const options = args.at(-2) as { token: string; host?: string };
|
||||
return withClient(options.host, async (client) =>
|
||||
result((await client.connectHub(url, options.token)).status),
|
||||
);
|
||||
}),
|
||||
);
|
||||
addJsonAndDaemonHostOptions(hub.command("status")).action(
|
||||
withOutput(async (...args) => {
|
||||
const options = args.at(-2) as { host?: string };
|
||||
return withClient(options.host, async (client) =>
|
||||
result((await client.getHubStatus()).status),
|
||||
);
|
||||
}),
|
||||
);
|
||||
addJsonAndDaemonHostOptions(
|
||||
hub
|
||||
.command("disconnect")
|
||||
.option("--force", "Remove local authority even if the Hub is offline"),
|
||||
).action(
|
||||
withOutput(async (...args) => {
|
||||
const options = args.at(-2) as { host?: string; force?: boolean };
|
||||
return withClient(options.host, async (client) => {
|
||||
const response = await client.disconnectHub(options.force ?? false);
|
||||
return result(response.status, response.warning);
|
||||
});
|
||||
}),
|
||||
);
|
||||
return hub;
|
||||
}
|
||||
Reference in New Issue
Block a user