Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Loading