-
Notifications
You must be signed in to change notification settings - Fork 7
Push & relay polish: actionable notifications, relay-everywhere, no-toggle UX #710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b4ccf74
d360151
75b4034
be7d8bd
30e8fdd
3730d12
ef191b0
bd0a63b
5ce9a69
bce629a
d47dab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,7 +99,7 @@ import type { BuiltInBrowserDesktopBridgeClient } from "./services/builtInBrowse | |
| import { resolveMachineAdeLayout } from "./services/projects/machineLayout"; | ||
| import { createPushRegistrationStore } from "./services/push/pushRegistrationStore"; | ||
| import { createPushRelayClient } from "./services/push/pushRelayClient"; | ||
| import { getSharedPushPublisherService, type PushPrNotification, type PushPublisherService } from "./services/push/pushPublisherService"; | ||
| import { getSharedPushPublisherService, resolvePushRelayStateFile, type PushPrNotification, type PushPublisherService } from "./services/push/pushPublisherService"; | ||
| import type { createFileService } from "../../desktop/src/main/services/files/fileService"; | ||
| import type { AppNavigationRequest, AppNavigationResult, PortLease } from "../../desktop/src/shared/types"; | ||
| import type { PrEventPayload } from "../../desktop/src/shared/types/prs"; | ||
|
|
@@ -1178,7 +1178,7 @@ export async function createAdeRuntime(args: { | |
| // push-identity file), so a run in one project doesn't clobber the phone's | ||
| // single "agent-runs" Live Activity for another. Each scope wires its own | ||
| // chat/pty/PR signals via attachSources; the aggregate merges runs across all. | ||
| const pushRelayFilePath = path.join(resolveMachineAdeLayout().secretsDir, "push-relay.json"); | ||
| const pushRelayFilePath = resolvePushRelayStateFile(resolveMachineAdeLayout().secretsDir); | ||
| const pushPublisherService = getSharedPushPublisherService(pushRelayFilePath, () => { | ||
| const store = createPushRegistrationStore({ filePath: pushRelayFilePath }); | ||
| return { | ||
|
|
@@ -1223,24 +1223,33 @@ export async function createAdeRuntime(args: { | |
| projectConfigService, | ||
| usageTrackingService, | ||
| }); | ||
| // Cloud tunnel relay (phone → Cloudflare DO → this brain). Off by default; | ||
| // the Settings toggle flips the shared store and the client follows. The | ||
| // store instance is shared with the sync service so the relay candidate in | ||
| // pairingConnectInfo and the tunnel client always agree on one config file. | ||
| // Cloud tunnel relay (phone → Cloudflare DO → this brain). On by default — | ||
| // the Settings kill-switch flips the shared store and the client follows. | ||
| // The store instance is shared with the sync service so the relay candidate | ||
| // in pairingConnectInfo and the tunnel client always agree on one config file. | ||
| const { createSyncCloudRelayStore } = await import("./services/sync/syncCloudRelayStore"); | ||
| const { createSyncTunnelClientService } = await import("./services/sync/syncTunnelClientService"); | ||
| const cloudRelayStore = createSyncCloudRelayStore({ | ||
| filePath: path.join( | ||
| resolvedArgs.syncRuntime?.phonePairingStateDir ?? resolveMachineAdeLayout().secretsDir, | ||
| "sync-cloud-relay.json", | ||
| ), | ||
| }); | ||
| const syncTunnelClientService = createSyncTunnelClientService({ | ||
| logger, | ||
| configStore: cloudRelayStore, | ||
| getSyncPort: () => resolvedArgs.syncRuntime?.sharedSyncListener?.getPort() ?? null, | ||
| }); | ||
| if (cloudRelayStore.isEnabled()) { | ||
| const { createSyncTunnelClientService, getSharedSyncTunnelClientService } = await import("./services/sync/syncTunnelClientService"); | ||
| const cloudRelayFilePath = path.join( | ||
| resolvedArgs.syncRuntime?.phonePairingStateDir ?? resolveMachineAdeLayout().secretsDir, | ||
| "sync-cloud-relay.json", | ||
| ); | ||
| const cloudRelayStore = createSyncCloudRelayStore({ filePath: cloudRelayFilePath }); | ||
| // ONE tunnel client per machine (keyed by the config file): per-scope | ||
| // instances would re-register the same machineKey with the relay on every | ||
| // project open and churn the connection paired phones dial through. | ||
| const syncTunnelClientService = getSharedSyncTunnelClientService(cloudRelayFilePath, () => | ||
| createSyncTunnelClientService({ | ||
| logger, | ||
| configStore: cloudRelayStore, | ||
| getSyncPort: () => resolvedArgs.syncRuntime?.sharedSyncListener?.getPort() ?? null, | ||
| })); | ||
| // Only the runtime that actually hosts phone sync (owns the brain-level | ||
| // shared listener) may register the relay tunnel. The relay DO keeps ONE | ||
| // host socket per machineKey (last wins), so a headless one-shot CLI | ||
| // runtime or embedded fallback starting the tunnel would steal the relay | ||
| // from `ade serve` and then fail every phone /connect (no sync port). | ||
| const canHostRelayTunnel = resolvedArgs.syncRuntime?.sharedSyncListener != null; | ||
| if (canHostRelayTunnel && cloudRelayStore.isEnabled()) { | ||
|
Comment on lines
+1231
to
+1252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== bootstrap slice ==\n'
sed -n '1200,1335p' apps/ade-cli/src/bootstrap.ts
printf '\n== syncTunnelClientService file map ==\n'
ast-grep outline apps/ade-cli/src/services/sync/syncTunnelClientService.ts --view expanded
printf '\n== syncTunnelClientService source ==\n'
cat -n apps/ade-cli/src/services/sync/syncTunnelClientService.ts | sed -n '1,260p'
printf '\n== search shared tunnel helpers usages ==\n'
rg -n "getSharedSyncTunnelClientService|peekSharedSyncTunnelClientService|createSyncTunnelClientService|syncTunnelClientService" apps/ade-cli/src -g '!**/dist/**'Repository: arul28/ADE Length of output: 19869 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== syncTunnelClientService tail ==\n'
sed -n '255,380p' apps/ade-cli/src/services/sync/syncTunnelClientService.ts
printf '\n== cli.ts usage around peekSharedSyncTunnelClientService ==\n'
sed -n '13545,13605p' apps/ade-cli/src/cli.ts
printf '\n== search for syncRuntime/sharedSyncListener in bootstrap and cli ==\n'
rg -n "syncRuntime|sharedSyncListener|phonePairingStateDir|initializeInBackground|runtimeKind" apps/ade-cli/src/bootstrap.ts apps/ade-cli/src/cli.tsRepository: arul28/ADE Length of output: 9576 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== bootstrap call sites ==\n'
rg -n "bootstrap\(" apps/ade-cli/src
printf '\n== serve/runtime selection around syncRuntime ==\n'
rg -n "sharedSyncListener|initializeInBackground|runtimeKind|headless|desktop" apps/ade-cli/src/cli.ts apps/ade-cli/src/bootstrap.tsRepository: arul28/ADE Length of output: 176 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== bootstrap file outline ==\n'
ast-grep outline apps/ade-cli/src/bootstrap.ts --view expanded | sed -n '1,220p'
printf '\n== top of bootstrap.ts ==\n'
sed -n '1,140p' apps/ade-cli/src/bootstrap.tsRepository: arul28/ADE Length of output: 10847 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1330,1395p' apps/ade-cli/src/bootstrap.tsRepository: arul28/ADE Length of output: 1845 Move shared tunnel creation behind 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| void syncTunnelClientService.start().catch((error) => { | ||
| logger.warn("sync.tunnel_start_failed", { | ||
| error: error instanceof Error ? error.message : String(error), | ||
|
|
@@ -1294,6 +1303,9 @@ export async function createAdeRuntime(args: { | |
| getModelPickerStore: () => getSharedModelPickerStore(db), | ||
| cloudRelayStore, | ||
| onCloudRelayEnabledChanged: (enabled) => { | ||
| // Same gate as startup: only the sync-hosting runtime may register | ||
| // the relay tunnel (see canHostRelayTunnel above). | ||
| if (enabled && !canHostRelayTunnel) return; | ||
| const action = enabled ? syncTunnelClientService.start() : syncTunnelClientService.stop(); | ||
| void action.catch((error) => { | ||
| logger.warn("sync.tunnel_toggle_failed", { | ||
|
|
@@ -1394,7 +1406,9 @@ export async function createAdeRuntime(args: { | |
| swallow(() => prPollingService.dispose()); | ||
| // Detach only this scope's signals; the shared publisher outlives the scope. | ||
| swallow(() => detachPushSources()); | ||
| void syncTunnelClientService.dispose().catch(() => {}); | ||
| // The tunnel client is machine-level and shared across scopes — closing | ||
| // one project must not sever the relay for the others. The daemon's | ||
| // shutdown path (disposeServeResources) stops it. | ||
| swallow(() => automationIngressService?.dispose()); | ||
| swallow(() => automationService?.dispose()); | ||
| swallow(() => usageTrackingService.dispose()); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.