import { type LiveActivity } from "expo-widgets";import Constants from "expo-constants";import * as Notifications from "expo-notifications";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema";import { AppState, Platform } from "react-native";import type { EnvironmentId } from "@t3tools/contracts";import { type RelayDeviceRegistrationRequest, type RelayAgentActivitySnapshotResponse, type RelayLiveActivityRegistrationRequest,} from "@t3tools/contracts/relay";import { findErrorTraceId } from "@t3tools/client-runtime/errors";import { ManagedRelay } from "@t3tools/client-runtime/relay";import { isAtomCommandInterrupted, settleAsyncResult, squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime"; import type { SavedRemoteConnection } from "../../lib/connection";import { runtime } from "../../lib/runtime";import { appAtomRegistry } from "../../state/atom-registry";import { environmentServerConfigsAtom } from "../../state/server";import type { Preferences } from "../../persistence/mobile-preferences";import { clearAgentAwarenessRegistrationRecord, loadAgentAwarenessDeviceId, loadAgentAwarenessRegistrationRecord, loadOrCreateAgentAwarenessDeviceId, loadPreferences, saveAgentAwarenessRegistrationRecord,} from "../../persistence/imperative";import AgentActivity, { type AgentActivityProps } from "../../widgets/AgentActivity";import { resolveCloudPublicConfig } from "../cloud/publicConfig";import { supportsAgentAwarenessPush } from "./capabilities";import { makeRelayDeviceRegistrationRequest, resolveApsEnvironment } from "./registrationPayload"; const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000; const AgentAwarenessOperation = Schema.Literals([ "read-notification-permissions", "read-native-push-token", "read-device-registration-relay-token", "read-device-unregistration-relay-token", "read-live-activity-registration-relay-token", "load-device-registration-identifier", "load-device-registration-preferences", "load-device-unregistration-identifier", "read-live-activity-push-token", "load-live-activity-registration-identifier", "list-active-live-activities", "load-live-activity-prime-preferences", "prime-live-activity",]); export class AgentAwarenessOperationError extends Schema.TaggedErrorClass<AgentAwarenessOperationError>()( "AgentAwarenessOperationError", { operation: AgentAwarenessOperation, cause: Schema.Defect(), },) { override get message(): string { return `Agent awareness operation ${this.operation} failed.`; }} const environmentConnections = new Map<EnvironmentId, SavedRemoteConnection>();const activityPushTokenListeners = new WeakSet<LiveActivity<AgentActivityProps>>();// Activity tokens the relay recently accepted, by acceptance time. The refresh// runs on sign-in, every app foreground, and every environment-connection// update, which arrive in bursts and spammed identical registrations. But the// registration is not a pure no-op: the relay replays the current aggregate to// this device on every accepted registration, and that replay is the// foreground reconciliation that repairs drifted or orphaned activities. So// dedupe only within a short window — bursts collapse to one request, while a// foreground after real time away still triggers a replay. Cleared on// sign-out/identity change alongside the device registration state.const ACTIVITY_TOKEN_REREGISTER_INTERVAL_MS = 60_000;const registeredActivityPushTokens = new Map<string, number>();let pushTokenSubscription: { remove: () => void } | null = null;let appStateSubscription: { remove: () => void } | null = null; // Whether the relay has actually accepted this device's registration. The// notification/Live Activity settings toggles must reflect this rather than// only local iOS permission or saved preferences: if the registration request// never succeeded, the device cannot receive anything, so the switches must// not read as enabled.export type AgentAwarenessRegistrationStatus = "unknown" | "pending" | "registered" | "failed";let registrationStatus: AgentAwarenessRegistrationStatus = "unknown";const registrationStatusListeners = new Set<() => void>(); function setRegistrationStatus(next: AgentAwarenessRegistrationStatus): void { if (registrationStatus === next) { return; } registrationStatus = next; for (const listener of registrationStatusListeners) { listener(); }} export function getAgentAwarenessRegistrationStatus(): AgentAwarenessRegistrationStatus { return registrationStatus;} export function subscribeAgentAwarenessRegistrationStatus(listener: () => void): () => void { registrationStatusListeners.add(listener); return () => { registrationStatusListeners.delete(listener); };}let activeLiveActivityRegistrationRetry: ReturnType<typeof setTimeout> | null = null;let relayTokenProvider: (() => Promise<string | null>) | null = null;let relayTokenProviderIdentity: string | null = null;let deviceRegistrationGeneration = 0;let activeDeviceRegistration: { readonly input: DeviceRegistrationInput; operation: Promise<void>;} | null = null;let pendingDeviceRegistration: { readonly input: DeviceRegistrationInput; readonly context: string;} | null = null; interface DeviceRegistrationInput { readonly observedPushToken?: string;} interface RegisterDeviceInput extends DeviceRegistrationInput { readonly preferencesOverride?: Partial<Preferences>;} export function mergeAgentAwarenessRegistrationPreferences( stored: Preferences, override: Partial<Preferences> | undefined,): Preferences { return { ...stored, ...override };} export function normalizeAgentAwarenessRelayBaseUrl( value: string | null | undefined,): string | null { const trimmed = value?.trim(); if (!trimmed) { return null; } return trimmed.replace(/\/+$/g, "");} function readRelayConfig(): { readonly url: string } | null { const relayUrl = resolveCloudPublicConfig().relay.url; if (!relayUrl) { logRegistrationDebug("relay registration skipped; relay config missing"); return null; } return { url: relayUrl };} function canRegisterRemoteLiveActivities(): boolean { return Platform.OS === "ios";} export function shouldRegisterAgentAwarenessDeviceForProvider( previousIdentity: string | null, identity: string | undefined,): boolean { return identity === undefined || identity !== previousIdentity;} export function setAgentAwarenessRelayTokenProvider( provider: (() => Promise<string | null>) | null, identity?: string,): void { const isExistingIdentity = provider !== null && !shouldRegisterAgentAwarenessDeviceForProvider(relayTokenProviderIdentity, identity); if (!isExistingIdentity) { deviceRegistrationGeneration++; activeDeviceRegistration = null; pendingDeviceRegistration = null; registeredActivityPushTokens.clear(); } relayTokenProvider = provider; relayTokenProviderIdentity = provider ? (identity ?? null) : null; if (!provider) { pushTokenSubscription?.remove(); pushTokenSubscription = null; appStateSubscription?.remove(); appStateSubscription = null; if (activeLiveActivityRegistrationRetry) { clearTimeout(activeLiveActivityRegistrationRetry); activeLiveActivityRegistrationRetry = null; } // Without a signed-in user the relay can no longer update or end these // activities, so they would sit orphaned on the lock screen. endLocalLiveActivities("live activity cleanup after cloud sign-out failed"); setRegistrationStatus("unknown"); // Sign-out is the only thing that invalidates a stored registration, so the // next sign-in re-registers. void clearAgentAwarenessRegistrationRecord().catch((error: unknown) => { logRegistrationError("clear registration record on sign-out failed", error); }); return; } ensurePushTokenListener(); ensureAppStateListener(); runRegistrationInBackground( refreshActiveLiveActivityRemoteRegistration(), "active live activity registration after cloud sign-in failed", ); if (isExistingIdentity) { // Same account re-activating (e.g. Clerk token refresh) normally needs no // re-registration — but if the previous attempt never succeeded, this is // the only trigger that will retry it before the next cold start. if (registrationStatus !== "registered") { enqueueDeviceRegistration({}, "device registration retry after cloud session refresh failed"); } return; } enqueueDeviceRegistration({}, "device registration after cloud sign-in failed");} // Detach the provider and native listeners without the destructive sign-out// cleanup. For provider teardown while the user is still signed in (e.g. the// auth bridge unmounting/remounting), ending lock-screen activities and wiping// the persisted registration would be wrong — the relay still holds a valid// registration and the next mount reuses it.export function releaseAgentAwarenessRelayTokenProvider(): void { relayTokenProvider = null; relayTokenProviderIdentity = null; pushTokenSubscription?.remove(); pushTokenSubscription = null; appStateSubscription?.remove(); appStateSubscription = null; if (activeLiveActivityRegistrationRetry) { clearTimeout(activeLiveActivityRegistrationRetry); activeLiveActivityRegistrationRetry = null; }} function iosMajorVersion(): number { const version = Platform.Version; if (typeof version === "number") { return Math.floor(version); } const major = Number.parseInt(version.split(".")[0] ?? "", 10); return Number.isFinite(major) ? major : 18;} function nativePushTokenRegistration(observedPushToken?: string) { return Effect.gen(function* () { if (!canRegisterRemoteLiveActivities() || !supportsAgentAwarenessPush()) { return { notificationsEnabled: false, pushToken: null }; } if (observedPushToken) { return { notificationsEnabled: true, pushToken: observedPushToken }; } const permissions = yield* Effect.tryPromise({ try: () => Notifications.getPermissionsAsync(), catch: (cause) => new AgentAwarenessOperationError({ operation: "read-notification-permissions", cause, }), }); if (!permissions.granted) { return { notificationsEnabled: false, pushToken: null }; } const token = yield* Effect.tryPromise({ try: () => Notifications.getDevicePushTokenAsync(), catch: (cause) => new AgentAwarenessOperationError({ operation: "read-native-push-token", cause, }), }).pipe( Effect.tapError((error) => Effect.sync(() => { logRegistrationError("native APNs token lookup failed", error); }), ), Effect.orElseSucceed(() => null), ); const pushToken = token?.type === "ios" && typeof token.data === "string" && token.data.trim().length > 0 ? token.data.trim() : null; return { notificationsEnabled: pushToken !== null, pushToken }; });} const relayToken = ( operation: "read-device-registration-relay-token" | "read-live-activity-registration-relay-token",) => Effect.gen(function* () { const provider = relayTokenProvider; if (!provider) { return null; } return yield* Effect.tryPromise({ try: provider, catch: (cause) => new AgentAwarenessOperationError({ operation, cause }), }); }); // Stable fingerprint of everything the relay stores for this device. When it// matches the last accepted registration for the same account, re-registering// is a no-op, so a launch that changed nothing skips the request entirely.function registrationSignature(body: RelayDeviceRegistrationRequest): string { return [ body.deviceId, body.pushToken ?? "", body.bundleId ?? "", body.apsEnvironment ?? "", body.appVersion ?? "", body.label, body.iosMajorVersion, body.preferences.notificationsEnabled, body.preferences.liveActivitiesEnabled, body.preferences.notifyOnApproval, body.preferences.notifyOnInput, body.preferences.notifyOnCompletion, body.preferences.notifyOnFailure, ].join("|");} function registerDeviceWithRelay( body: RelayDeviceRegistrationRequest, expectedGeneration: number,): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { if (expectedGeneration !== deviceRegistrationGeneration) { logRegistrationDebug("device registration cancelled before relay request", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } const relayConfig = readRelayConfig(); if (!relayConfig) { // Nothing is in flight and nothing can succeed until configuration // appears; "pending" would otherwise stick forever. setRegistrationStatus("unknown"); return; } const token = yield* relayToken("read-device-registration-relay-token"); if (expectedGeneration !== deviceRegistrationGeneration) { logRegistrationDebug("device registration cancelled after auth lookup", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } if (!token) { logRegistrationDebug("relay device registration skipped; user is not signed in"); setRegistrationStatus("unknown"); return; } // Skip the request when this account already registered an identical // payload; the relay upsert would be a no-op. The record is only cleared on // sign-out, so a device stays registered across launches without re-hitting // the relay every time the app opens. const identity = relayTokenProviderIdentity ?? ""; const persisted = yield* Effect.tryPromise({ try: () => loadAgentAwarenessRegistrationRecord(), catch: (cause) => cause, }).pipe(Effect.orElseSucceed(() => null)); if (expectedGeneration !== deviceRegistrationGeneration) { // Signed out while the record loaded — do not resurrect the cleared // record or report the previous account's registration as current. logRegistrationDebug("device registration cancelled after record lookup", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } const payload = body; // The relay URL participates so pointing the app at a different relay // invalidates the record and re-registers there. const signature = `${relayConfig.url}|${registrationSignature(payload)}`; if (persisted && persisted.identity === identity && persisted.signature === signature) { setRegistrationStatus("registered"); logRegistrationDebug("relay device registration skipped; already registered for account", { expectedGeneration, }); return; } const client = yield* ManagedRelay.ManagedRelayClient; logRegistrationDebug("relay device registration request started", { expectedGeneration, }); yield* client.registerDevice({ clerkToken: token, payload, }); if (expectedGeneration !== deviceRegistrationGeneration) { // Signed out while the request was in flight: the sign-out path already // reset the status and cleared the record for the next account, so a // stale success must not overwrite either. logRegistrationDebug("device registration completed after sign-out; result discarded", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } setRegistrationStatus("registered"); yield* Effect.promise(() => saveAgentAwarenessRegistrationRecord({ identity, signature, }).catch((error: unknown) => { logRegistrationError("persist registration record failed", error); }), ); logRegistrationDebug("relay device registration request completed", { expectedGeneration, }); });} function unregisterDeviceWithRelay(input: { readonly deviceId: string; readonly tokenProvider: () => Promise<string | null>;}): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { if (!readRelayConfig()) return; const token = yield* Effect.tryPromise({ try: input.tokenProvider, catch: (cause) => new AgentAwarenessOperationError({ operation: "read-device-unregistration-relay-token", cause, }), }); if (!token) { logRegistrationDebug("relay device unregistration skipped; user is not signed in"); return; } const client = yield* ManagedRelay.ManagedRelayClient; yield* client.unregisterDevice({ clerkToken: token, deviceId: input.deviceId, }); });} // The environment descriptor advertises whether agent-activity publishes// currently leave that server (`capabilities.agentActivityPublishing`). Only// an explicit false skips the seed card: older servers omit the capability// but may still publish.function environmentPublishesAgentActivity(environmentId: EnvironmentId): boolean { return ( appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities .agentActivityPublishing !== false );} // Arms the lock-screen card the moment the user starts agent work from this// phone, while the app is still foregrounded and the fresh activity's token// can be registered immediately. The seeded row is a best-effort placeholder;// the relay's registration replay repaints it with the authoritative// aggregate within seconds. No-ops when a card is already armed, and skips// environments that report publishing disabled — the seed would sit on// "Connecting" forever with no update ever arriving to repaint or end it.export function armAgentAwarenessLiveActivityForLocalWork(input: { readonly environmentId: EnvironmentId; readonly threadTitle: string; readonly projectTitle: string;}): void { if (!canRegisterRemoteLiveActivities() || !relayTokenProvider) { return; } if (!environmentPublishesAgentActivity(input.environmentId)) { logRegistrationDebug("live activity arming skipped; environment does not publish", { environmentId: input.environmentId, }); return; } void loadPreferences() .catch(() => null) .then((preferences) => { if (preferences?.liveActivitiesEnabled === false) { return; } armAgentAwarenessLiveActivityForLocalWorkNow(input); });} function armAgentAwarenessLiveActivityForLocalWorkNow(input: { readonly threadTitle: string; readonly projectTitle: string;}): void { try { if (AgentActivity.getInstances().length > 0) {Lines 1–500 of 1140. Load more
import { type LiveActivity } from "expo-widgets"; import Constants from "expo-constants"; import * as Notifications from "expo-notifications"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { AppState, Platform } from "react-native"; import type { EnvironmentId } from "@t3tools/contracts"; import { type RelayDeviceRegistrationRequest, type RelayAgentActivitySnapshotResponse, type RelayLiveActivityRegistrationRequest, } from "@t3tools/contracts/relay"; import { findErrorTraceId } from "@t3tools/client-runtime/errors"; import { ManagedRelay } from "@t3tools/client-runtime/relay"; import { isAtomCommandInterrupted, settleAsyncResult, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import type { SavedRemoteConnection } from "../../lib/connection"; import { runtime } from "../../lib/runtime"; import { appAtomRegistry } from "../../state/atom-registry"; import { environmentServerConfigsAtom } from "../../state/server"; import type { Preferences } from "../../persistence/mobile-preferences"; import { clearAgentAwarenessRegistrationRecord, loadAgentAwarenessDeviceId, loadAgentAwarenessRegistrationRecord, loadOrCreateAgentAwarenessDeviceId, loadPreferences, saveAgentAwarenessRegistrationRecord, } from "../../persistence/imperative"; import AgentActivity, { type AgentActivityProps } from "../../widgets/AgentActivity"; import { resolveCloudPublicConfig } from "../cloud/publicConfig"; import { supportsAgentAwarenessPush } from "./capabilities"; import { makeRelayDeviceRegistrationRequest, resolveApsEnvironment } from "./registrationPayload"; const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000; const AgentAwarenessOperation = Schema.Literals([ "read-notification-permissions", "read-native-push-token", "read-device-registration-relay-token", "read-device-unregistration-relay-token", "read-live-activity-registration-relay-token", "load-device-registration-identifier", "load-device-registration-preferences", "load-device-unregistration-identifier", "read-live-activity-push-token", "load-live-activity-registration-identifier", "list-active-live-activities", "load-live-activity-prime-preferences", "prime-live-activity", ]); export class AgentAwarenessOperationError extends Schema.TaggedErrorClass<AgentAwarenessOperationError>()( "AgentAwarenessOperationError", { operation: AgentAwarenessOperation, cause: Schema.Defect(), }, ) { override get message(): string { return `Agent awareness operation ${this.operation} failed.`; } } const environmentConnections = new Map<EnvironmentId, SavedRemoteConnection>(); const activityPushTokenListeners = new WeakSet<LiveActivity<AgentActivityProps>>(); // Activity tokens the relay recently accepted, by acceptance time. The refresh // runs on sign-in, every app foreground, and every environment-connection // update, which arrive in bursts and spammed identical registrations. But the // registration is not a pure no-op: the relay replays the current aggregate to // this device on every accepted registration, and that replay is the // foreground reconciliation that repairs drifted or orphaned activities. So // dedupe only within a short window — bursts collapse to one request, while a // foreground after real time away still triggers a replay. Cleared on // sign-out/identity change alongside the device registration state. const ACTIVITY_TOKEN_REREGISTER_INTERVAL_MS = 60_000; const registeredActivityPushTokens = new Map<string, number>(); let pushTokenSubscription: { remove: () => void } | null = null; let appStateSubscription: { remove: () => void } | null = null; // Whether the relay has actually accepted this device's registration. The // notification/Live Activity settings toggles must reflect this rather than // only local iOS permission or saved preferences: if the registration request // never succeeded, the device cannot receive anything, so the switches must // not read as enabled. export type AgentAwarenessRegistrationStatus = "unknown" | "pending" | "registered" | "failed"; let registrationStatus: AgentAwarenessRegistrationStatus = "unknown"; const registrationStatusListeners = new Set<() => void>(); function setRegistrationStatus(next: AgentAwarenessRegistrationStatus): void { if (registrationStatus === next) { return; } registrationStatus = next; for (const listener of registrationStatusListeners) { listener(); } } export function getAgentAwarenessRegistrationStatus(): AgentAwarenessRegistrationStatus { return registrationStatus; } export function subscribeAgentAwarenessRegistrationStatus(listener: () => void): () => void { registrationStatusListeners.add(listener); return () => { registrationStatusListeners.delete(listener); }; } let activeLiveActivityRegistrationRetry: ReturnType<typeof setTimeout> | null = null; let relayTokenProvider: (() => Promise<string | null>) | null = null; let relayTokenProviderIdentity: string | null = null; let deviceRegistrationGeneration = 0; let activeDeviceRegistration: { readonly input: DeviceRegistrationInput; operation: Promise<void>; } | null = null; let pendingDeviceRegistration: { readonly input: DeviceRegistrationInput; readonly context: string; } | null = null; interface DeviceRegistrationInput { readonly observedPushToken?: string; } interface RegisterDeviceInput extends DeviceRegistrationInput { readonly preferencesOverride?: Partial<Preferences>; } export function mergeAgentAwarenessRegistrationPreferences( stored: Preferences, override: Partial<Preferences> | undefined, ): Preferences { return { ...stored, ...override }; } export function normalizeAgentAwarenessRelayBaseUrl( value: string | null | undefined, ): string | null { const trimmed = value?.trim(); if (!trimmed) { return null; } return trimmed.replace(/\/+$/g, ""); } function readRelayConfig(): { readonly url: string } | null { const relayUrl = resolveCloudPublicConfig().relay.url; if (!relayUrl) { logRegistrationDebug("relay registration skipped; relay config missing"); return null; } return { url: relayUrl }; } function canRegisterRemoteLiveActivities(): boolean { return Platform.OS === "ios"; } export function shouldRegisterAgentAwarenessDeviceForProvider( previousIdentity: string | null, identity: string | undefined, ): boolean { return identity === undefined || identity !== previousIdentity; } export function setAgentAwarenessRelayTokenProvider( provider: (() => Promise<string | null>) | null, identity?: string, ): void { const isExistingIdentity = provider !== null && !shouldRegisterAgentAwarenessDeviceForProvider(relayTokenProviderIdentity, identity); if (!isExistingIdentity) { deviceRegistrationGeneration++; activeDeviceRegistration = null; pendingDeviceRegistration = null; registeredActivityPushTokens.clear(); } relayTokenProvider = provider; relayTokenProviderIdentity = provider ? (identity ?? null) : null; if (!provider) { pushTokenSubscription?.remove(); pushTokenSubscription = null; appStateSubscription?.remove(); appStateSubscription = null; if (activeLiveActivityRegistrationRetry) { clearTimeout(activeLiveActivityRegistrationRetry); activeLiveActivityRegistrationRetry = null; } // Without a signed-in user the relay can no longer update or end these // activities, so they would sit orphaned on the lock screen. endLocalLiveActivities("live activity cleanup after cloud sign-out failed"); setRegistrationStatus("unknown"); // Sign-out is the only thing that invalidates a stored registration, so the // next sign-in re-registers. void clearAgentAwarenessRegistrationRecord().catch((error: unknown) => { logRegistrationError("clear registration record on sign-out failed", error); }); return; } ensurePushTokenListener(); ensureAppStateListener(); runRegistrationInBackground( refreshActiveLiveActivityRemoteRegistration(), "active live activity registration after cloud sign-in failed", ); if (isExistingIdentity) { // Same account re-activating (e.g. Clerk token refresh) normally needs no // re-registration — but if the previous attempt never succeeded, this is // the only trigger that will retry it before the next cold start. if (registrationStatus !== "registered") { enqueueDeviceRegistration({}, "device registration retry after cloud session refresh failed"); } return; } enqueueDeviceRegistration({}, "device registration after cloud sign-in failed"); } // Detach the provider and native listeners without the destructive sign-out // cleanup. For provider teardown while the user is still signed in (e.g. the // auth bridge unmounting/remounting), ending lock-screen activities and wiping // the persisted registration would be wrong — the relay still holds a valid // registration and the next mount reuses it. export function releaseAgentAwarenessRelayTokenProvider(): void { relayTokenProvider = null; relayTokenProviderIdentity = null; pushTokenSubscription?.remove(); pushTokenSubscription = null; appStateSubscription?.remove(); appStateSubscription = null; if (activeLiveActivityRegistrationRetry) { clearTimeout(activeLiveActivityRegistrationRetry); activeLiveActivityRegistrationRetry = null; } } function iosMajorVersion(): number { const version = Platform.Version; if (typeof version === "number") { return Math.floor(version); } const major = Number.parseInt(version.split(".")[0] ?? "", 10); return Number.isFinite(major) ? major : 18; } function nativePushTokenRegistration(observedPushToken?: string) { return Effect.gen(function* () { if (!canRegisterRemoteLiveActivities() || !supportsAgentAwarenessPush()) { return { notificationsEnabled: false, pushToken: null }; } if (observedPushToken) { return { notificationsEnabled: true, pushToken: observedPushToken }; } const permissions = yield* Effect.tryPromise({ try: () => Notifications.getPermissionsAsync(), catch: (cause) => new AgentAwarenessOperationError({ operation: "read-notification-permissions", cause, }), }); if (!permissions.granted) { return { notificationsEnabled: false, pushToken: null }; } const token = yield* Effect.tryPromise({ try: () => Notifications.getDevicePushTokenAsync(), catch: (cause) => new AgentAwarenessOperationError({ operation: "read-native-push-token", cause, }), }).pipe( Effect.tapError((error) => Effect.sync(() => { logRegistrationError("native APNs token lookup failed", error); }), ), Effect.orElseSucceed(() => null), ); const pushToken = token?.type === "ios" && typeof token.data === "string" && token.data.trim().length > 0 ? token.data.trim() : null; return { notificationsEnabled: pushToken !== null, pushToken }; }); } const relayToken = ( operation: "read-device-registration-relay-token" | "read-live-activity-registration-relay-token", ) => Effect.gen(function* () { const provider = relayTokenProvider; if (!provider) { return null; } return yield* Effect.tryPromise({ try: provider, catch: (cause) => new AgentAwarenessOperationError({ operation, cause }), }); }); // Stable fingerprint of everything the relay stores for this device. When it // matches the last accepted registration for the same account, re-registering // is a no-op, so a launch that changed nothing skips the request entirely. function registrationSignature(body: RelayDeviceRegistrationRequest): string { return [ body.deviceId, body.pushToken ?? "", body.bundleId ?? "", body.apsEnvironment ?? "", body.appVersion ?? "", body.label, body.iosMajorVersion, body.preferences.notificationsEnabled, body.preferences.liveActivitiesEnabled, body.preferences.notifyOnApproval, body.preferences.notifyOnInput, body.preferences.notifyOnCompletion, body.preferences.notifyOnFailure, ].join("|"); } function registerDeviceWithRelay( body: RelayDeviceRegistrationRequest, expectedGeneration: number, ): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { if (expectedGeneration !== deviceRegistrationGeneration) { logRegistrationDebug("device registration cancelled before relay request", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } const relayConfig = readRelayConfig(); if (!relayConfig) { // Nothing is in flight and nothing can succeed until configuration // appears; "pending" would otherwise stick forever. setRegistrationStatus("unknown"); return; } const token = yield* relayToken("read-device-registration-relay-token"); if (expectedGeneration !== deviceRegistrationGeneration) { logRegistrationDebug("device registration cancelled after auth lookup", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } if (!token) { logRegistrationDebug("relay device registration skipped; user is not signed in"); setRegistrationStatus("unknown"); return; } // Skip the request when this account already registered an identical // payload; the relay upsert would be a no-op. The record is only cleared on // sign-out, so a device stays registered across launches without re-hitting // the relay every time the app opens. const identity = relayTokenProviderIdentity ?? ""; const persisted = yield* Effect.tryPromise({ try: () => loadAgentAwarenessRegistrationRecord(), catch: (cause) => cause, }).pipe(Effect.orElseSucceed(() => null)); if (expectedGeneration !== deviceRegistrationGeneration) { // Signed out while the record loaded — do not resurrect the cleared // record or report the previous account's registration as current. logRegistrationDebug("device registration cancelled after record lookup", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } const payload = body; // The relay URL participates so pointing the app at a different relay // invalidates the record and re-registers there. const signature = `${relayConfig.url}|${registrationSignature(payload)}`; if (persisted && persisted.identity === identity && persisted.signature === signature) { setRegistrationStatus("registered"); logRegistrationDebug("relay device registration skipped; already registered for account", { expectedGeneration, }); return; } const client = yield* ManagedRelay.ManagedRelayClient; logRegistrationDebug("relay device registration request started", { expectedGeneration, }); yield* client.registerDevice({ clerkToken: token, payload, }); if (expectedGeneration !== deviceRegistrationGeneration) { // Signed out while the request was in flight: the sign-out path already // reset the status and cleared the record for the next account, so a // stale success must not overwrite either. logRegistrationDebug("device registration completed after sign-out; result discarded", { expectedGeneration, currentGeneration: deviceRegistrationGeneration, }); return; } setRegistrationStatus("registered"); yield* Effect.promise(() => saveAgentAwarenessRegistrationRecord({ identity, signature, }).catch((error: unknown) => { logRegistrationError("persist registration record failed", error); }), ); logRegistrationDebug("relay device registration request completed", { expectedGeneration, }); }); } function unregisterDeviceWithRelay(input: { readonly deviceId: string; readonly tokenProvider: () => Promise<string | null>; }): Effect.Effect<void, unknown, ManagedRelay.ManagedRelayClient> { return Effect.gen(function* () { if (!readRelayConfig()) return; const token = yield* Effect.tryPromise({ try: input.tokenProvider, catch: (cause) => new AgentAwarenessOperationError({ operation: "read-device-unregistration-relay-token", cause, }), }); if (!token) { logRegistrationDebug("relay device unregistration skipped; user is not signed in"); return; } const client = yield* ManagedRelay.ManagedRelayClient; yield* client.unregisterDevice({ clerkToken: token, deviceId: input.deviceId, }); }); } // The environment descriptor advertises whether agent-activity publishes // currently leave that server (`capabilities.agentActivityPublishing`). Only // an explicit false skips the seed card: older servers omit the capability // but may still publish. function environmentPublishesAgentActivity(environmentId: EnvironmentId): boolean { return ( appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities .agentActivityPublishing !== false ); } // Arms the lock-screen card the moment the user starts agent work from this // phone, while the app is still foregrounded and the fresh activity's token // can be registered immediately. The seeded row is a best-effort placeholder; // the relay's registration replay repaints it with the authoritative // aggregate within seconds. No-ops when a card is already armed, and skips // environments that report publishing disabled — the seed would sit on // "Connecting" forever with no update ever arriving to repaint or end it. export function armAgentAwarenessLiveActivityForLocalWork(input: { readonly environmentId: EnvironmentId; readonly threadTitle: string; readonly projectTitle: string; }): void { if (!canRegisterRemoteLiveActivities() || !relayTokenProvider) { return; } if (!environmentPublishesAgentActivity(input.environmentId)) { logRegistrationDebug("live activity arming skipped; environment does not publish", { environmentId: input.environmentId, }); return; } void loadPreferences() .catch(() => null) .then((preferences) => { if (preferences?.liveActivitiesEnabled === false) { return; } armAgentAwarenessLiveActivityForLocalWorkNow(input); }); } function armAgentAwarenessLiveActivityForLocalWorkNow(input: { readonly threadTitle: string; readonly projectTitle: string; }): void { try { if (AgentActivity.getInstances().length > 0) {