Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .ade/ade.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,3 @@ ai:
endpoint: http://127.0.0.1:1234
autoDetect: true
preferredModelId: null
notifications:
apns:
enabled: true
env: sandbox
keyId: 8HYA5AWCGP
teamId: VQ372F39G6
bundleId: com.ade.ios
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added mobile push notifications through APNS, external MCP OAuth, auto-rebase suggestions, PR issue resolver, smart tooltips, and the diagnostics dashboard.
- Added external MCP OAuth, auto-rebase suggestions, PR issue resolver, smart tooltips, and the diagnostics dashboard.
- Added legacy Cursor integration and OpenCode runtime integration for managed AI backends.

## [1.0.18] - 2026-04-14
Expand Down
42 changes: 0 additions & 42 deletions apps/ade-cli/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ import { createAutomationIngressService } from "../../desktop/src/main/services/
import { createAutomationSecretService } from "../../desktop/src/main/services/automations/automationSecretService";
import type { createGithubService } from "../../desktop/src/main/services/github/githubService";
import { createFeedbackReporterService } from "../../desktop/src/main/services/feedback/feedbackReporterService";
import {
ApnsKeyStore,
ApnsService,
} from "../../desktop/src/main/services/notifications/apnsService";
import {
ADE_AGENT_SKILLS_DIRS_ENV,
getAdeAgentSkillRootsForPrompt,
Expand Down Expand Up @@ -231,8 +227,6 @@ export type AdeRuntime = {
builtInBrowserService?: BuiltInBrowserService | BuiltInBrowserDesktopBridgeClient | null;
syncHostService?: ReturnType<typeof createSyncHostService> | null;
syncService?: ReturnType<typeof createSyncService> | null;
apnsService?: ApnsService | null;
apnsKeyStore?: ApnsKeyStore | null;
automationIngressService?: ReturnType<typeof createAutomationIngressService> | null;
feedbackReporterService?: ReturnType<typeof createFeedbackReporterService> | null;
usageTrackingService?: ReturnType<typeof createUsageTrackingService> | null;
Expand Down Expand Up @@ -1158,39 +1152,6 @@ export async function createAdeRuntime(args: {
projectConfigService,
usageTrackingService,
});
const apnsService = new ApnsService({ logger });
const projectSecretsDir = path.join(projectRoot, ".ade", "secrets");
const apnsKeyStore = new ApnsKeyStore({
encryptedKeyPath: path.join(projectSecretsDir, "apns.key.enc"),
credentialStore: new EncryptedFileCredentialStore({
secretsDir: projectSecretsDir,
}),
});
try {
const apnsConfig = projectConfigService.get().effective.notifications?.apns;
if (
apnsConfig?.enabled &&
apnsKeyStore.has() &&
apnsConfig.keyId &&
apnsConfig.teamId &&
apnsConfig.bundleId
) {
const pem = apnsKeyStore.load();
if (pem) {
apnsService.configure({
keyP8Pem: pem,
keyId: apnsConfig.keyId,
teamId: apnsConfig.teamId,
bundleId: apnsConfig.bundleId,
env: apnsConfig.env ?? "sandbox",
});
}
}
} catch (error) {
logger.warn("apns.configure_on_startup_failed", {
error: error instanceof Error ? error.message : String(error),
});
}
let syncService: ReturnType<typeof createSyncService> | null = null;
if (resolvedArgs.syncRuntime?.enabled && agentChatService) {
const { createSyncService } = await import("./services/sync/syncService");
Expand Down Expand Up @@ -1287,8 +1248,6 @@ export async function createAdeRuntime(args: {
diffService,
syncService,
syncHostService: syncService?.getHostService() ?? null,
apnsService,
apnsKeyStore,
laneWorktreeLockService,
ptyService,
testService,
Expand Down Expand Up @@ -1338,7 +1297,6 @@ export async function createAdeRuntime(args: {
swallow(() => automationIngressService?.dispose());
swallow(() => automationService?.dispose());
swallow(() => usageTrackingService.dispose());
swallow(() => apnsService.dispose());
swallow(() => syncService?.dispose());
swallow(() => processService.disposeAll());
swallow(() => runtimeDiagnosticsService.dispose());
Expand Down
177 changes: 0 additions & 177 deletions apps/ade-cli/src/services/sync/deviceRegistryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
SyncPeerMetadata,
SyncPeerPlatform,
} from "../../../../desktop/src/shared/types";
import { normalizeNotificationPreferences, type NotificationPreferences } from "../../../../desktop/src/shared/types/sync";
import type { Logger } from "../../../../desktop/src/main/services/logging/logger";
import { mapPlatform } from "./syncProtocol";
import { resolveTailscaleCliPath } from "./resolveTailscaleCliPath";
Expand Down Expand Up @@ -53,7 +52,6 @@ type ClusterStateRow = {

const DEVICE_ID_FILE = "sync-device-id";
export const DEFAULT_SYNC_CLUSTER_ID = "default";
const WORKSPACE_ACTIVITY_ID = "workspace";
const TAILSCALE_STATUS_CACHE_MS = 30_000;

let tailscaleStatusCache:
Expand Down Expand Up @@ -477,174 +475,6 @@ export function createDeviceRegistryService(args: DeviceRegistryServiceArgs) {
});
};

type ApnsTokenKind = "alert" | "activity-start" | "activity-update";

const apnsMetaKey = (kind: ApnsTokenKind): string => {
if (kind === "alert") return "apnsAlertToken";
if (kind === "activity-start") return "apnsActivityStartToken";
return "apnsActivityUpdateTokens";
};

const setApnsToken = (
deviceId: string,
token: string,
kind: ApnsTokenKind,
env: "sandbox" | "production",
extras: { bundleId?: string; activityId?: string } = {},
): SyncDeviceRecord | null => {
const device = getDevice(deviceId);
if (!device) return null;
const nextMetadata: Record<string, unknown> = {
...device.metadata,
apnsEnv: env,
apnsTokenUpdatedAt: nowIso(),
};
if (extras.bundleId) nextMetadata.apnsBundleId = extras.bundleId;
if (kind === "activity-update") {
const existing = (device.metadata.apnsActivityUpdateTokens as Record<string, string> | undefined) ?? {};
const activityId = extras.activityId?.trim() || WORKSPACE_ACTIVITY_ID;
nextMetadata.apnsActivityUpdateTokens = { ...existing, [activityId]: token };
} else {
nextMetadata[apnsMetaKey(kind)] = token;
}
return upsertDeviceRecord({
deviceId: device.deviceId,
siteId: device.siteId,
name: device.name,
platform: device.platform,
deviceType: device.deviceType,
lastSeenAt: device.lastSeenAt,
lastHost: device.lastHost,
lastPort: device.lastPort,
tailscaleIp: device.tailscaleIp,
ipAddresses: device.ipAddresses,
metadata: nextMetadata,
});
};

const getApnsTokenForDevice = (
deviceId: string,
kind: ApnsTokenKind,
activityId?: string,
): string | null => {
const device = getDevice(deviceId);
if (!device) return null;
if (kind === "activity-update") {
const map = (device.metadata.apnsActivityUpdateTokens as Record<string, string> | undefined) ?? {};
return map[activityId?.trim() || WORKSPACE_ACTIVITY_ID] ?? null;
}
const raw = device.metadata[apnsMetaKey(kind)];
return typeof raw === "string" && raw.trim().length > 0 ? raw : null;
};

const setNotificationPreferences = (
deviceId: string,
prefs: NotificationPreferences,
): SyncDeviceRecord | null => {
const device = getDevice(deviceId);
if (!device) return null;
const normalizedPrefs = normalizeNotificationPreferences(prefs);
return upsertDeviceRecord({
deviceId: device.deviceId,
siteId: device.siteId,
name: device.name,
platform: device.platform,
deviceType: device.deviceType,
lastSeenAt: device.lastSeenAt,
lastHost: device.lastHost,
lastPort: device.lastPort,
tailscaleIp: device.tailscaleIp,
ipAddresses: device.ipAddresses,
metadata: {
...device.metadata,
notificationPreferences: normalizedPrefs,
notificationPreferencesUpdatedAt: nowIso(),
},
});
};

const getNotificationPreferences = (deviceId: string): NotificationPreferences | null => {
const prefs = getDevice(deviceId)?.metadata.notificationPreferences;
if (!prefs || typeof prefs !== "object" || Array.isArray(prefs)) return null;
return normalizeNotificationPreferences(prefs);
};

const invalidateApnsToken = (deviceToken: string): void => {
const token = deviceToken.trim();
if (!token) return;
const device = findDeviceByApnsToken(token);
if (!device) return;
const nextMetadata = { ...device.metadata };
if (nextMetadata.apnsAlertToken === token) {
delete nextMetadata.apnsAlertToken;
}
if (nextMetadata.apnsActivityStartToken === token) {
delete nextMetadata.apnsActivityStartToken;
}
const updates = nextMetadata.apnsActivityUpdateTokens;
if (updates && typeof updates === "object" && !Array.isArray(updates)) {
const nextUpdates = { ...(updates as Record<string, string>) };
for (const [activityId, value] of Object.entries(nextUpdates)) {
if (value === token) delete nextUpdates[activityId];
}
if (Object.keys(nextUpdates).length > 0) {
nextMetadata.apnsActivityUpdateTokens = nextUpdates;
} else {
delete nextMetadata.apnsActivityUpdateTokens;
}
}
upsertDeviceRecord({
deviceId: device.deviceId,
siteId: device.siteId,
name: device.name,
platform: device.platform,
deviceType: device.deviceType,
lastSeenAt: device.lastSeenAt,
lastHost: device.lastHost,
lastPort: device.lastPort,
tailscaleIp: device.tailscaleIp,
ipAddresses: device.ipAddresses,
metadata: nextMetadata,
});
};

const invalidateApnsTokensForDevice = (deviceId: string): void => {
const device = getDevice(deviceId);
if (!device) return;
const nextMetadata = { ...device.metadata };
delete nextMetadata.apnsAlertToken;
delete nextMetadata.apnsActivityStartToken;
delete nextMetadata.apnsActivityUpdateTokens;
upsertDeviceRecord({
deviceId: device.deviceId,
siteId: device.siteId,
name: device.name,
platform: device.platform,
deviceType: device.deviceType,
lastSeenAt: device.lastSeenAt,
lastHost: device.lastHost,
lastPort: device.lastPort,
tailscaleIp: device.tailscaleIp,
ipAddresses: device.ipAddresses,
metadata: nextMetadata,
});
};

const findDeviceByApnsToken = (token: string): SyncDeviceRecord | null => {
for (const device of listDevices()) {
const alert = device.metadata.apnsAlertToken;
const activity = device.metadata.apnsActivityStartToken;
if (alert === token || activity === token) return device;
const updates = device.metadata.apnsActivityUpdateTokens;
if (updates && typeof updates === "object") {
for (const value of Object.values(updates as Record<string, unknown>)) {
if (value === token) return device;
}
}
}
return null;
};

const applyBrainStatus = (payload: SyncBrainStatusPayload): void => {
upsertPeerMetadata(payload.brain, { lastSeenAt: nowIso() });
for (const peer of payload.connectedPeers) {
Expand Down Expand Up @@ -698,13 +528,6 @@ export function createDeviceRegistryService(args: DeviceRegistryServiceArgs) {
applyBrainStatus,
clearClusterRegistryForViewerJoin,
forgetDevice,
setApnsToken,
getApnsTokenForDevice,
setNotificationPreferences,
getNotificationPreferences,
invalidateApnsToken,
invalidateApnsTokensForDevice,
findDeviceByApnsToken,
};
}

Expand Down
Loading