authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 15:13:48-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 15:13:48-07:00
log97de7d7e1535ddf485ea8806c639f27e99cfbcb2
tree2d576773da8b048b3a1d60773c5d05c73bc8bea6
parentd7718e81e0af33d0a2d0f515385ac210f352fffb
signature Signed by PGP key B5690EEEBB952194

[codex] Structure agent awareness registration errors (#3328)

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 110 insertions(+), 20 deletions(-)

apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts+23
......@@ -18,6 +18,7 @@ import { cryptoLayer } from "../cloud/dpop";
1818import { managedRelayClientLayer } from "../cloud/managedRelayLayer";
1919import { makeRelayDeviceRegistrationRequest } from "./registrationPayload";
2020import {
21 AgentAwarenessOperationError,
2122 __resetAgentAwarenessRemoteRegistrationForTest,
2223 refreshActiveLiveActivityRemoteRegistration,
2324 refreshAgentAwarenessRegistration,
......@@ -267,6 +268,28 @@ describe("makeRelayDeviceRegistrationRequest", () => {
267268 }).pipe(Effect.provide(relayTestLayer));
268269 });
269270
271 it.effect("preserves Live Activity push-token lookup failures", () => {
272 const cause = new Error("native token lookup failed");
273 const activity = {
274 getPushToken: vi.fn(() => Promise.reject(cause)),
275 addPushTokenListener: vi.fn(),
276 };
277
278 return Effect.gen(function* () {
279 const error = yield* Effect.flip(
280 registerLiveActivityPushToken({ activity: activity as never }),
281 );
282
283 expect(error).toBeInstanceOf(AgentAwarenessOperationError);
284 expect(error).toMatchObject({
285 _tag: "AgentAwarenessOperationError",
286 operation: "read-live-activity-push-token",
287 cause,
288 message: "Agent awareness operation read-live-activity-push-token failed.",
289 });
290 }).pipe(Effect.provide(relayTestLayer));
291 });
292
270293 it.effect(
271294 "reports Live Activity token registration as skipped when relay auth is unavailable",
272295 () => {
apps/mobile/src/features/agent-awareness/remoteRegistration.ts+87-20
......@@ -2,6 +2,7 @@ import { addPushToStartTokenListener, type LiveActivity } from "expo-widgets";
22import Constants from "expo-constants";
33import * as Notifications from "expo-notifications";
44import * as Effect from "effect/Effect";
5import * as Schema from "effect/Schema";
56import { Platform } from "react-native";
67import type { EnvironmentId } from "@t3tools/contracts";
78import {
......@@ -28,6 +29,33 @@ import { resolveCloudPublicConfig } from "../cloud/publicConfig";
2829import { makeRelayDeviceRegistrationRequest } from "./registrationPayload";
2930
3031const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000;
32
33const AgentAwarenessOperation = Schema.Literals([
34 "read-notification-permissions",
35 "read-native-push-token",
36 "read-device-registration-relay-token",
37 "read-device-unregistration-relay-token",
38 "read-live-activity-registration-relay-token",
39 "load-device-registration-identifier",
40 "load-device-registration-preferences",
41 "load-device-unregistration-identifier",
42 "read-live-activity-push-token",
43 "load-live-activity-registration-identifier",
44 "list-active-live-activities",
45]);
46
47export class AgentAwarenessOperationError extends Schema.TaggedErrorClass<AgentAwarenessOperationError>()(
48 "AgentAwarenessOperationError",
49 {
50 operation: AgentAwarenessOperation,
51 cause: Schema.Defect(),
52 },
53) {
54 override get message(): string {
55 return `Agent awareness operation ${this.operation} failed.`;
56 }
57}
58
3159const environmentConnections = new Map<EnvironmentId, SavedRemoteConnection>();
3260const activityPushTokenListeners = new WeakSet<LiveActivity<AgentActivityProps>>();
3361let pushToStartSubscription: { remove: () => void } | null = null;
......@@ -137,14 +165,22 @@ function nativePushTokenRegistration(observedPushToken?: string) {
137165 }
138166 const permissions = yield* Effect.tryPromise({
139167 try: () => Notifications.getPermissionsAsync(),
140 catch: (error) => error,
168 catch: (cause) =>
169 new AgentAwarenessOperationError({
170 operation: "read-notification-permissions",
171 cause,
172 }),
141173 });
142174 if (!permissions.granted) {
143175 return { notificationsEnabled: false, pushToken: null };
144176 }
145177 const token = yield* Effect.tryPromise({
146178 try: () => Notifications.getDevicePushTokenAsync(),
147 catch: (error) => error,
179 catch: (cause) =>
180 new AgentAwarenessOperationError({
181 operation: "read-native-push-token",
182 cause,
183 }),
148184 }).pipe(
149185 Effect.tapError((error) =>
150186 Effect.sync(() => {
......@@ -161,16 +197,19 @@ function nativePushTokenRegistration(observedPushToken?: string) {
161197 });
162198}
163199
164const relayToken = Effect.gen(function* () {
165 const provider = relayTokenProvider;
166 if (!provider) {
167 return null;
168 }
169 return yield* Effect.tryPromise({
170 try: provider,
171 catch: (error) => error,
200const relayToken = (
201 operation: "read-device-registration-relay-token" | "read-live-activity-registration-relay-token",
202) =>
203 Effect.gen(function* () {
204 const provider = relayTokenProvider;
205 if (!provider) {
206 return null;
207 }
208 return yield* Effect.tryPromise({
209 try: provider,
210 catch: (cause) => new AgentAwarenessOperationError({ operation, cause }),
211 });
172212 });
173});
174213
175214function registerDeviceWithRelay(
176215 body: RelayDeviceRegistrationRequest,
......@@ -185,7 +224,7 @@ function registerDeviceWithRelay(
185224 return;
186225 }
187226 if (!readRelayConfig()) return;
188 const token = yield* relayToken;
227 const token = yield* relayToken("read-device-registration-relay-token");
189228 if (expectedGeneration !== deviceRegistrationGeneration) {
190229 logRegistrationDebug("device registration cancelled after auth lookup", {
191230 expectedGeneration,
......@@ -220,7 +259,11 @@ function unregisterDeviceWithRelay(input: {
220259 if (!readRelayConfig()) return;
221260 const token = yield* Effect.tryPromise({
222261 try: input.tokenProvider,
223 catch: (error) => error,
262 catch: (cause) =>
263 new AgentAwarenessOperationError({
264 operation: "read-device-unregistration-relay-token",
265 cause,
266 }),
224267 });
225268 if (!token) {
226269 logRegistrationDebug("relay device unregistration skipped; user is not signed in");
......@@ -240,7 +283,7 @@ function registerLiveActivityWithRelay(
240283): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> {
241284 return Effect.gen(function* () {
242285 if (!readRelayConfig()) return false;
243 const token = yield* relayToken;
286 const token = yield* relayToken("read-live-activity-registration-relay-token");
244287 if (!token) {
245288 logRegistrationDebug("relay live activity registration skipped; user is not signed in");
246289 return false;
......@@ -381,11 +424,19 @@ function registerDevice(
381424 const [deviceId, preferences] = yield* Effect.all([
382425 Effect.tryPromise({
383426 try: () => loadOrCreateAgentAwarenessDeviceId(),
384 catch: (error) => error,
427 catch: (cause) =>
428 new AgentAwarenessOperationError({
429 operation: "load-device-registration-identifier",
430 cause,
431 }),
385432 }),
386433 Effect.tryPromise({
387434 try: () => loadPreferences(),
388 catch: (error) => error,
435 catch: (cause) =>
436 new AgentAwarenessOperationError({
437 operation: "load-device-registration-preferences",
438 cause,
439 }),
389440 }),
390441 ]);
391442 const pushTokenRegistration = yield* nativePushTokenRegistration(input?.observedPushToken);
......@@ -519,7 +570,11 @@ export function unregisterAgentAwarenessDeviceForCurrentUser(
519570 return Effect.gen(function* () {
520571 const deviceId = yield* Effect.tryPromise({
521572 try: () => loadAgentAwarenessDeviceId(),
522 catch: (error) => error,
573 catch: (cause) =>
574 new AgentAwarenessOperationError({
575 operation: "load-device-unregistration-identifier",
576 cause,
577 }),
523578 });
524579 if (!deviceId) {
525580 return;
......@@ -544,7 +599,11 @@ export function registerLiveActivityPushToken(input: {
544599
545600 const activityPushToken = yield* Effect.tryPromise({
546601 try: () => input.activity.getPushToken(),
547 catch: (error) => error,
602 catch: (cause) =>
603 new AgentAwarenessOperationError({
604 operation: "read-live-activity-push-token",
605 cause,
606 }),
548607 });
549608 if (!activityPushToken) {
550609 if (activityPushTokenListeners.has(input.activity)) {
......@@ -592,7 +651,11 @@ function registerLiveActivityPushTokenValue(input: {
592651 return Effect.gen(function* () {
593652 const deviceId = yield* Effect.tryPromise({
594653 try: () => loadOrCreateAgentAwarenessDeviceId(),
595 catch: (error) => error,
654 catch: (cause) =>
655 new AgentAwarenessOperationError({
656 operation: "load-live-activity-registration-identifier",
657 cause,
658 }),
596659 });
597660 const registered = yield* registerLiveActivityWithRelay({
598661 deviceId,
......@@ -633,7 +696,11 @@ export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect<
633696
634697 const activities = yield* Effect.try({
635698 try: () => AgentActivity.getInstances(),
636 catch: (error) => error,
699 catch: (cause) =>
700 new AgentAwarenessOperationError({
701 operation: "list-active-live-activities",
702 cause,
703 }),
637704 }).pipe(
638705 Effect.catch((error) =>
639706 Effect.sync(() => {