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:
Mohamed Boudra
2026-07-17 20:20:57 +02:00
committed by GitHub
parent 39cb3dbb9c
commit a414f8ea85
46 changed files with 5799 additions and 80 deletions

View File

@@ -221,6 +221,25 @@ test("advertises consumer-provided browser automation capabilities", async () =>
});
});
test("Hub management requires daemon support before dispatching requests", async () => {
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "hub_feature_gate_unit_test",
transportFactory: () => mock.transport,
reconnect: { enabled: false },
});
clients.push(client);
const connecting = client.connect();
mock.triggerOpen();
await connecting;
await expect(client.getHubStatus()).rejects.toThrow(
"Update the host to use Hub relationship management.",
);
expect(mock.sent).toEqual([]);
});
test("sets the complete viewed timeline subscription only when the daemon supports it", async () => {
const supportedTransport = createMockTransport();
const supportedClient = new DaemonClient({

View File

@@ -4269,6 +4269,33 @@ export class DaemonClient {
});
}
async connectHub(hubUrl: string, token: string, requestId?: string) {
this.requireHubRelationshipSupport();
return this.sendCorrelatedSessionRequest({
requestId,
message: { type: "hub.management.daemon.connect.request", hubUrl, token },
responseType: "hub.management.daemon.connect.response",
});
}
async getHubStatus(requestId?: string) {
this.requireHubRelationshipSupport();
return this.sendCorrelatedSessionRequest({
requestId,
message: { type: "hub.management.daemon.get_status.request" },
responseType: "hub.management.daemon.get_status.response",
});
}
async disconnectHub(force = false, requestId?: string) {
this.requireHubRelationshipSupport();
return this.sendCorrelatedSessionRequest({
requestId,
message: { type: "hub.management.daemon.disconnect.request", force },
responseType: "hub.management.daemon.disconnect.response",
});
}
async getDaemonPairingOffer(
options?: DaemonPairingOfferOptions,
): Promise<DaemonPairingOfferPayload> {
@@ -5089,6 +5116,13 @@ export class DaemonClient {
return this.lastServerInfoMessage;
}
private requireHubRelationshipSupport(): void {
// COMPAT(hubRelationship): added in v0.1.X, drop the gate when floor >= v0.1.X.
if (this.lastServerInfoMessage?.features?.hubRelationship !== true) {
throw new Error("Update the host to use Hub relationship management.");
}
}
private resolveTransportUrlForAttempt(): string {
return this.config.url;
}