@ateam-ai-mobile/expo 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +16 -0
- package/android/src/main/java/expo/modules/adavision/AdaVisionModule.kt +75 -0
- package/dist/attachmentCache-DhQTqi2E.d.mts +18 -0
- package/dist/attachmentCache-DhQTqi2E.d.ts +18 -0
- package/dist/bootstrap.d.mts +95 -0
- package/dist/bootstrap.d.ts +95 -0
- package/dist/bootstrap.js +2473 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bootstrap.mjs +64 -0
- package/dist/bootstrap.mjs.map +1 -0
- package/dist/chunk-4BMWVN25.mjs +91 -0
- package/dist/chunk-4BMWVN25.mjs.map +1 -0
- package/dist/chunk-4XYXJYN5.mjs +1513 -0
- package/dist/chunk-4XYXJYN5.mjs.map +1 -0
- package/dist/chunk-5HM6UA2K.mjs +571 -0
- package/dist/chunk-5HM6UA2K.mjs.map +1 -0
- package/dist/chunk-6LXF3JVX.mjs +96 -0
- package/dist/chunk-6LXF3JVX.mjs.map +1 -0
- package/dist/chunk-FF6ZZZXH.mjs +71 -0
- package/dist/chunk-FF6ZZZXH.mjs.map +1 -0
- package/dist/chunk-KOXF2FZM.mjs +92 -0
- package/dist/chunk-KOXF2FZM.mjs.map +1 -0
- package/dist/chunk-L4V7OCJ3.mjs +71 -0
- package/dist/chunk-L4V7OCJ3.mjs.map +1 -0
- package/dist/chunk-TZXWVYYR.mjs +67 -0
- package/dist/chunk-TZXWVYYR.mjs.map +1 -0
- package/dist/chunk-VQJPSLHP.mjs +1801 -0
- package/dist/chunk-VQJPSLHP.mjs.map +1 -0
- package/dist/chunk-WMJKH4XE.mjs +34 -0
- package/dist/chunk-WMJKH4XE.mjs.map +1 -0
- package/dist/chunk-XQSY6MHP.mjs +213 -0
- package/dist/chunk-XQSY6MHP.mjs.map +1 -0
- package/dist/createBridge.mjs +2 -0
- package/dist/createBridge.mjs.map +1 -1
- package/dist/device/createDeviceProvider.d.mts +23 -0
- package/dist/device/createDeviceProvider.d.ts +23 -0
- package/dist/device/createDeviceProvider.js +967 -0
- package/dist/device/createDeviceProvider.js.map +1 -0
- package/dist/device/createDeviceProvider.mjs +17 -0
- package/dist/device/createDeviceProvider.mjs.map +1 -0
- package/dist/device-bridge/collectors/appEngagement.d.mts +10 -0
- package/dist/device-bridge/collectors/appEngagement.d.ts +10 -0
- package/dist/device-bridge/collectors/appEngagement.js +128 -0
- package/dist/device-bridge/collectors/appEngagement.js.map +1 -0
- package/dist/device-bridge/collectors/appEngagement.mjs +12 -0
- package/dist/device-bridge/collectors/appEngagement.mjs.map +1 -0
- package/dist/device-bridge/collectors/health.d.mts +24 -0
- package/dist/device-bridge/collectors/health.d.ts +24 -0
- package/dist/device-bridge/collectors/health.js +238 -0
- package/dist/device-bridge/collectors/health.js.map +1 -0
- package/dist/device-bridge/collectors/health.mjs +18 -0
- package/dist/device-bridge/collectors/health.mjs.map +1 -0
- package/dist/device-bridge/cookies.d.mts +49 -0
- package/dist/device-bridge/cookies.d.ts +49 -0
- package/dist/device-bridge/cookies.js +90 -0
- package/dist/device-bridge/cookies.js.map +1 -0
- package/dist/device-bridge/cookies.mjs +14 -0
- package/dist/device-bridge/cookies.mjs.map +1 -0
- package/dist/device-bridge/index.d.mts +483 -0
- package/dist/device-bridge/index.d.ts +483 -0
- package/dist/device-bridge/index.js +2084 -0
- package/dist/device-bridge/index.js.map +1 -0
- package/dist/device-bridge/index.mjs +32 -0
- package/dist/device-bridge/index.mjs.map +1 -0
- package/dist/device-bridge/nfc.d.mts +30 -0
- package/dist/device-bridge/nfc.d.ts +30 -0
- package/dist/device-bridge/nfc.js +109 -0
- package/dist/device-bridge/nfc.js.map +1 -0
- package/dist/device-bridge/nfc.mjs +14 -0
- package/dist/device-bridge/nfc.mjs.map +1 -0
- package/dist/index.d.mts +493 -18
- package/dist/index.d.ts +493 -18
- package/dist/index.js +1409 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -194
- package/dist/index.mjs.map +1 -1
- package/dist/oauth/expoOAuth.d.mts +18 -0
- package/dist/oauth/expoOAuth.d.ts +18 -0
- package/dist/oauth/expoOAuth.js +102 -0
- package/dist/oauth/expoOAuth.js.map +1 -0
- package/dist/oauth/expoOAuth.mjs +8 -0
- package/dist/oauth/expoOAuth.mjs.map +1 -0
- package/dist/types-BRanJYTn.d.mts +138 -0
- package/dist/types-BRanJYTn.d.ts +138 -0
- package/dist/vision/index.d.mts +81 -0
- package/dist/vision/index.d.ts +81 -0
- package/dist/vision/index.js +101 -0
- package/dist/vision/index.js.map +1 -0
- package/dist/vision/index.mjs +28 -0
- package/dist/vision/index.mjs.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AdaVision.podspec +28 -0
- package/ios/AdaVisionModule.swift +432 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/analytics/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/metadata.json +773 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/analytics/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/model.mil +720 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/weights/weight.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model1/coremldata.bin +0 -0
- package/package.json +107 -7
- package/src/bootstrap.ts +159 -0
- package/src/camera/CameraTaskHost.tsx +175 -0
- package/src/camera/cameraTasks.ts +129 -0
- package/src/device/createDeviceProvider.ts +832 -0
- package/src/device-bridge/actions/calendar-actions.ts +217 -0
- package/src/device-bridge/actions/dnd.ts +74 -0
- package/src/device-bridge/actions/health-actions.ts +58 -0
- package/src/device-bridge/actions/index.ts +12 -0
- package/src/device-bridge/actions/messaging.ts +74 -0
- package/src/device-bridge/actions/navigation.ts +88 -0
- package/src/device-bridge/actions/queue-poller.ts +151 -0
- package/src/device-bridge/background.ts +137 -0
- package/src/device-bridge/biometrics.ts +64 -0
- package/src/device-bridge/capabilities.ts +218 -0
- package/src/device-bridge/collectors/appEngagement.ts +120 -0
- package/src/device-bridge/collectors/battery.ts +83 -0
- package/src/device-bridge/collectors/calendar.ts +162 -0
- package/src/device-bridge/collectors/camera.ts +27 -0
- package/src/device-bridge/collectors/connectivity.ts +60 -0
- package/src/device-bridge/collectors/contacts.ts +200 -0
- package/src/device-bridge/collectors/health.ts +299 -0
- package/src/device-bridge/collectors/index.ts +12 -0
- package/src/device-bridge/collectors/location.ts +224 -0
- package/src/device-bridge/collectors/mood.ts +141 -0
- package/src/device-bridge/collectors/notifications.ts +70 -0
- package/src/device-bridge/cookies.ts +89 -0
- package/src/device-bridge/dynamic-connector.ts +422 -0
- package/src/device-bridge/index.ts +467 -0
- package/src/device-bridge/nfc.ts +85 -0
- package/src/device-bridge/plugin.ts +71 -0
- package/src/device-bridge/relay-client.ts +237 -0
- package/src/device-bridge/scanner.ts +42 -0
- package/src/device-bridge/sharing.ts +35 -0
- package/src/device-bridge/types.ts +326 -0
- package/src/device-state/activityClassifier.ts +151 -0
- package/src/device-state/activityEngine.ts +252 -0
- package/src/device-state/collectors.ts +31 -0
- package/src/device-state/domains/activity.ts +93 -0
- package/src/device-state/domains/capability.ts +60 -0
- package/src/device-state/domains/connectivity.ts +60 -0
- package/src/device-state/domains/deviceContext.ts +55 -0
- package/src/device-state/domains/interaction.ts +59 -0
- package/src/device-state/domains/spatial.ts +283 -0
- package/src/device-state/domains/visual.ts +100 -0
- package/src/device-state/headingSource.ts +84 -0
- package/src/device-state/index.ts +77 -0
- package/src/device-state/quality.ts +60 -0
- package/src/device-state/replay.ts +220 -0
- package/src/device-state/turnDetector.ts +121 -0
- package/src/device-state/visualEngine.ts +341 -0
- package/src/haptics.ts +4 -0
- package/src/index.ts +37 -0
- package/src/oauth/expoOAuth.tsx +113 -0
- package/src/vision/analysis.js +80 -0
- package/src/vision/index.ts +144 -0
- package/src/vision/ring.js +35 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Action Executor — creates/modifies events via expo-calendar.
|
|
3
|
+
* Executes 'calendar.create', 'calendar.reschedule', 'calendar.cancel' actions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as ExpoCalendar from 'expo-calendar';
|
|
7
|
+
// Type-only alias: the value namespace above cannot be used in type
|
|
8
|
+
// position, so `ExpoCalendar.Calendar`-style annotations need this.
|
|
9
|
+
// Derived rather than imported — see collectors/calendar.ts.
|
|
10
|
+
type Event = Awaited<ReturnType<typeof ExpoCalendar.getEventsAsync>>[number];
|
|
11
|
+
import type { ActionResult } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the default calendar ID for creating events.
|
|
15
|
+
*/
|
|
16
|
+
async function getDefaultCalendarId(): Promise<string | null> {
|
|
17
|
+
const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);
|
|
18
|
+
|
|
19
|
+
// Prefer the default calendar
|
|
20
|
+
const defaultCal = calendars.find(c => c.isPrimary) || calendars[0];
|
|
21
|
+
return defaultCal?.id || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute a calendar.create action.
|
|
26
|
+
*
|
|
27
|
+
* Params expected:
|
|
28
|
+
* - title: string
|
|
29
|
+
* - date: string (YYYY-MM-DD)
|
|
30
|
+
* - start_time: string (HH:MM)
|
|
31
|
+
* - end_time?: string (HH:MM)
|
|
32
|
+
* - location?: string
|
|
33
|
+
* - attendees?: string[] (names — no email lookup here)
|
|
34
|
+
* - notes?: string
|
|
35
|
+
*/
|
|
36
|
+
export async function executeCreate(
|
|
37
|
+
actionId: string,
|
|
38
|
+
params: Record<string, unknown>
|
|
39
|
+
): Promise<ActionResult> {
|
|
40
|
+
const title = params.title as string;
|
|
41
|
+
const date = params.date as string;
|
|
42
|
+
const startTime = params.start_time as string;
|
|
43
|
+
const endTime = params.end_time as string | undefined;
|
|
44
|
+
|
|
45
|
+
if (!title || !date || !startTime) {
|
|
46
|
+
return {
|
|
47
|
+
id: actionId,
|
|
48
|
+
ok: false,
|
|
49
|
+
error: 'Missing title, date, or start_time',
|
|
50
|
+
executed_at: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const calendarId = await getDefaultCalendarId();
|
|
56
|
+
if (!calendarId) {
|
|
57
|
+
return {
|
|
58
|
+
id: actionId,
|
|
59
|
+
ok: false,
|
|
60
|
+
error: 'No calendar found on device',
|
|
61
|
+
executed_at: new Date().toISOString(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
66
|
+
const [startH, startM] = startTime.split(':').map(Number);
|
|
67
|
+
|
|
68
|
+
const startDate = new Date(year, month - 1, day, startH, startM);
|
|
69
|
+
let endDate: Date;
|
|
70
|
+
|
|
71
|
+
if (endTime) {
|
|
72
|
+
const [endH, endM] = endTime.split(':').map(Number);
|
|
73
|
+
endDate = new Date(year, month - 1, day, endH, endM);
|
|
74
|
+
} else {
|
|
75
|
+
// Default 1 hour duration
|
|
76
|
+
endDate = new Date(startDate.getTime() + 60 * 60 * 1000);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const eventId = await ExpoCalendar.createEventAsync(calendarId, {
|
|
80
|
+
title,
|
|
81
|
+
startDate,
|
|
82
|
+
endDate,
|
|
83
|
+
location: (params.location as string) || undefined,
|
|
84
|
+
notes: (params.notes as string) || undefined,
|
|
85
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
id: actionId,
|
|
90
|
+
ok: true,
|
|
91
|
+
result: {
|
|
92
|
+
event_id: eventId,
|
|
93
|
+
title,
|
|
94
|
+
date,
|
|
95
|
+
start: startTime,
|
|
96
|
+
end: endTime || formatTime(endDate),
|
|
97
|
+
message: `Event "${title}" created successfully`,
|
|
98
|
+
},
|
|
99
|
+
executed_at: new Date().toISOString(),
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
id: actionId,
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `Calendar create failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
106
|
+
executed_at: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute a calendar.reschedule action.
|
|
113
|
+
*
|
|
114
|
+
* Params expected:
|
|
115
|
+
* - event_id: string
|
|
116
|
+
* - new_date?: string (YYYY-MM-DD)
|
|
117
|
+
* - new_start_time?: string (HH:MM)
|
|
118
|
+
* - new_end_time?: string (HH:MM)
|
|
119
|
+
*/
|
|
120
|
+
export async function executeReschedule(
|
|
121
|
+
actionId: string,
|
|
122
|
+
params: Record<string, unknown>
|
|
123
|
+
): Promise<ActionResult> {
|
|
124
|
+
const eventId = params.event_id as string;
|
|
125
|
+
|
|
126
|
+
if (!eventId) {
|
|
127
|
+
return {
|
|
128
|
+
id: actionId,
|
|
129
|
+
ok: false,
|
|
130
|
+
error: 'Missing event_id',
|
|
131
|
+
executed_at: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const updates: Partial<Event> = {};
|
|
137
|
+
|
|
138
|
+
if (params.new_date && params.new_start_time) {
|
|
139
|
+
const [year, month, day] = (params.new_date as string).split('-').map(Number);
|
|
140
|
+
const [h, m] = (params.new_start_time as string).split(':').map(Number);
|
|
141
|
+
updates.startDate = new Date(year, month - 1, day, h, m);
|
|
142
|
+
|
|
143
|
+
if (params.new_end_time) {
|
|
144
|
+
const [eh, em] = (params.new_end_time as string).split(':').map(Number);
|
|
145
|
+
updates.endDate = new Date(year, month - 1, day, eh, em);
|
|
146
|
+
} else {
|
|
147
|
+
updates.endDate = new Date(updates.startDate.getTime() + 60 * 60 * 1000);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await ExpoCalendar.updateEventAsync(eventId, updates);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
id: actionId,
|
|
155
|
+
ok: true,
|
|
156
|
+
result: { event_id: eventId, message: 'Event rescheduled successfully' },
|
|
157
|
+
executed_at: new Date().toISOString(),
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
id: actionId,
|
|
162
|
+
ok: false,
|
|
163
|
+
error: `Calendar reschedule failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
164
|
+
executed_at: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Execute a calendar.cancel action.
|
|
171
|
+
*
|
|
172
|
+
* Params expected:
|
|
173
|
+
* - event_id: string
|
|
174
|
+
*/
|
|
175
|
+
export async function executeCancel(
|
|
176
|
+
actionId: string,
|
|
177
|
+
params: Record<string, unknown>
|
|
178
|
+
): Promise<ActionResult> {
|
|
179
|
+
const eventId = params.event_id as string;
|
|
180
|
+
|
|
181
|
+
if (!eventId) {
|
|
182
|
+
return {
|
|
183
|
+
id: actionId,
|
|
184
|
+
ok: false,
|
|
185
|
+
error: 'Missing event_id',
|
|
186
|
+
executed_at: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await ExpoCalendar.deleteEventAsync(eventId);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
id: actionId,
|
|
195
|
+
ok: true,
|
|
196
|
+
result: { event_id: eventId, message: 'Event cancelled successfully' },
|
|
197
|
+
executed_at: new Date().toISOString(),
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return {
|
|
201
|
+
id: actionId,
|
|
202
|
+
ok: false,
|
|
203
|
+
error: `Calendar cancel failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
204
|
+
executed_at: new Date().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── Helpers ───
|
|
210
|
+
|
|
211
|
+
function formatTime(date: Date): string {
|
|
212
|
+
return date.toLocaleTimeString('en-US', {
|
|
213
|
+
hour: '2-digit',
|
|
214
|
+
minute: '2-digit',
|
|
215
|
+
hour12: false,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DND Action Executor — manages Do Not Disturb via expo-notifications.
|
|
3
|
+
* Executes 'dnd.set' actions from the action queue.
|
|
4
|
+
*
|
|
5
|
+
* Note: True system-level DND control requires platform-specific APIs:
|
|
6
|
+
* - iOS: Focus modes (requires entitlements)
|
|
7
|
+
* - Android: NotificationManager.setInterruptionFilter (requires DND access)
|
|
8
|
+
*
|
|
9
|
+
* For now, this manages DND state within the PA system:
|
|
10
|
+
* - Stores DND state in relay for the connector to read
|
|
11
|
+
* - Controls notification channel behavior in-app
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ActionResult, DndState } from '../types';
|
|
15
|
+
|
|
16
|
+
// In-memory DND state (synced to relay)
|
|
17
|
+
let currentDndState: DndState = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
schedule: { start: '22:00', end: '07:00' },
|
|
20
|
+
allowlist: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get current DND state.
|
|
25
|
+
*/
|
|
26
|
+
export function getState(): DndState {
|
|
27
|
+
return { ...currentDndState };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Execute a dnd.set action.
|
|
32
|
+
*
|
|
33
|
+
* Params expected:
|
|
34
|
+
* - enabled?: boolean
|
|
35
|
+
* - schedule_start?: string (HH:MM)
|
|
36
|
+
* - schedule_end?: string (HH:MM)
|
|
37
|
+
* - allowlist?: string[]
|
|
38
|
+
*/
|
|
39
|
+
export async function execute(
|
|
40
|
+
actionId: string,
|
|
41
|
+
params: Record<string, unknown>
|
|
42
|
+
): Promise<ActionResult> {
|
|
43
|
+
try {
|
|
44
|
+
if (params.enabled !== undefined) {
|
|
45
|
+
currentDndState.enabled = params.enabled as boolean;
|
|
46
|
+
}
|
|
47
|
+
if (params.schedule_start) {
|
|
48
|
+
currentDndState.schedule.start = params.schedule_start as string;
|
|
49
|
+
}
|
|
50
|
+
if (params.schedule_end) {
|
|
51
|
+
currentDndState.schedule.end = params.schedule_end as string;
|
|
52
|
+
}
|
|
53
|
+
if (params.allowlist) {
|
|
54
|
+
currentDndState.allowlist = params.allowlist as string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id: actionId,
|
|
59
|
+
ok: true,
|
|
60
|
+
result: {
|
|
61
|
+
message: `DND ${currentDndState.enabled ? 'enabled' : 'disabled'}`,
|
|
62
|
+
dnd: currentDndState,
|
|
63
|
+
},
|
|
64
|
+
executed_at: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
id: actionId,
|
|
69
|
+
ok: false,
|
|
70
|
+
error: `DND set failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
71
|
+
executed_at: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Action Executor — on-demand reads from HealthKit.
|
|
3
|
+
*
|
|
4
|
+
* Two action types:
|
|
5
|
+
* - 'health.today' → returns one HealthDayMetrics for today
|
|
6
|
+
* - 'health.history' → returns last N days (1..30), newest-first
|
|
7
|
+
*
|
|
8
|
+
* Both are read-only; no params are required for `today`.
|
|
9
|
+
* `history` accepts { days: number } (default 30).
|
|
10
|
+
*
|
|
11
|
+
* For periodic snapshots use the `health` capability instead — this
|
|
12
|
+
* executor exists so a connector can pull fresher / longer-range data
|
|
13
|
+
* without waiting for the next sync tick.
|
|
14
|
+
*/
|
|
15
|
+
import * as health from '../collectors/health';
|
|
16
|
+
import type { ActionResult } from '../types';
|
|
17
|
+
|
|
18
|
+
export async function executeToday(actionId: string): Promise<ActionResult> {
|
|
19
|
+
try {
|
|
20
|
+
const today = await health.collectToday();
|
|
21
|
+
return {
|
|
22
|
+
id: actionId,
|
|
23
|
+
ok: true,
|
|
24
|
+
result: { today },
|
|
25
|
+
executed_at: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
return {
|
|
29
|
+
id: actionId,
|
|
30
|
+
ok: false,
|
|
31
|
+
error: String(e?.message || e),
|
|
32
|
+
executed_at: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function executeHistory(
|
|
38
|
+
actionId: string,
|
|
39
|
+
params: Record<string, unknown>,
|
|
40
|
+
): Promise<ActionResult> {
|
|
41
|
+
try {
|
|
42
|
+
const days = Number(params?.days) || 30;
|
|
43
|
+
const list = await health.collectHistory(days);
|
|
44
|
+
return {
|
|
45
|
+
id: actionId,
|
|
46
|
+
ok: true,
|
|
47
|
+
result: { days: list },
|
|
48
|
+
executed_at: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
} catch (e: any) {
|
|
51
|
+
return {
|
|
52
|
+
id: actionId,
|
|
53
|
+
ok: false,
|
|
54
|
+
error: String(e?.message || e),
|
|
55
|
+
executed_at: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Executors — handle write operations queued by MCP skills.
|
|
3
|
+
* The phone polls for pending actions and executes them natively.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { QueuePoller } from './queue-poller';
|
|
7
|
+
export type { QueuePollerConfig } from './queue-poller';
|
|
8
|
+
export * as messaging from './messaging';
|
|
9
|
+
export * as calendarActions from './calendar-actions';
|
|
10
|
+
export * as dnd from './dnd';
|
|
11
|
+
export * as navigation from './navigation';
|
|
12
|
+
export * as healthActions from './health-actions';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging Action Executor — sends SMS via expo-sms.
|
|
3
|
+
* Executes 'messaging.send' actions from the action queue.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as SMS from 'expo-sms';
|
|
7
|
+
import type { ActionResult } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if SMS is available on this device.
|
|
11
|
+
*/
|
|
12
|
+
export async function isAvailable(): Promise<boolean> {
|
|
13
|
+
return SMS.isAvailableAsync();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Execute a messaging.send action.
|
|
18
|
+
*
|
|
19
|
+
* Params expected:
|
|
20
|
+
* - contact_name: string (for display only)
|
|
21
|
+
* - phone: string (phone number to send to)
|
|
22
|
+
* - message: string (message text)
|
|
23
|
+
*/
|
|
24
|
+
export async function execute(
|
|
25
|
+
actionId: string,
|
|
26
|
+
params: Record<string, unknown>
|
|
27
|
+
): Promise<ActionResult> {
|
|
28
|
+
const phone = params.phone as string;
|
|
29
|
+
const message = params.message as string;
|
|
30
|
+
|
|
31
|
+
if (!phone || !message) {
|
|
32
|
+
return {
|
|
33
|
+
id: actionId,
|
|
34
|
+
ok: false,
|
|
35
|
+
error: 'Missing phone or message parameter',
|
|
36
|
+
executed_at: new Date().toISOString(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const available = await SMS.isAvailableAsync();
|
|
42
|
+
if (!available) {
|
|
43
|
+
return {
|
|
44
|
+
id: actionId,
|
|
45
|
+
ok: false,
|
|
46
|
+
error: 'SMS not available on this device',
|
|
47
|
+
executed_at: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Opens the native SMS compose screen with pre-filled content
|
|
52
|
+
const { result } = await SMS.sendSMSAsync([phone], message);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
id: actionId,
|
|
56
|
+
ok: result === 'sent' || result === 'unknown', // 'unknown' on Android means it was sent
|
|
57
|
+
result: {
|
|
58
|
+
to: params.contact_name || phone,
|
|
59
|
+
phone,
|
|
60
|
+
message,
|
|
61
|
+
status: result,
|
|
62
|
+
sent_at: new Date().toISOString(),
|
|
63
|
+
},
|
|
64
|
+
executed_at: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
id: actionId,
|
|
69
|
+
ok: false,
|
|
70
|
+
error: `SMS send failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
71
|
+
executed_at: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation Action Executor — opens maps app via expo-linking.
|
|
3
|
+
* Executes 'navigation.start' actions from the action queue.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Linking from 'expo-linking';
|
|
7
|
+
import { Platform } from 'react-native';
|
|
8
|
+
import type { ActionResult } from '../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute a navigation.start action.
|
|
12
|
+
* Opens the native maps app with turn-by-turn directions.
|
|
13
|
+
*
|
|
14
|
+
* Params expected:
|
|
15
|
+
* - destination: string (address or place name)
|
|
16
|
+
* - mode?: 'driving' | 'transit' | 'walking' (default: 'driving')
|
|
17
|
+
*/
|
|
18
|
+
export async function execute(
|
|
19
|
+
actionId: string,
|
|
20
|
+
params: Record<string, unknown>
|
|
21
|
+
): Promise<ActionResult> {
|
|
22
|
+
const destination = params.destination as string;
|
|
23
|
+
const mode = (params.mode as string) || 'driving';
|
|
24
|
+
|
|
25
|
+
if (!destination) {
|
|
26
|
+
return {
|
|
27
|
+
id: actionId,
|
|
28
|
+
ok: false,
|
|
29
|
+
error: 'Missing destination parameter',
|
|
30
|
+
executed_at: new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const encodedDest = encodeURIComponent(destination);
|
|
36
|
+
let url: string;
|
|
37
|
+
|
|
38
|
+
if (Platform.OS === 'ios') {
|
|
39
|
+
// Apple Maps with directions
|
|
40
|
+
const modeMap: Record<string, string> = {
|
|
41
|
+
driving: 'd',
|
|
42
|
+
transit: 'r',
|
|
43
|
+
walking: 'w',
|
|
44
|
+
};
|
|
45
|
+
url = `maps://maps.apple.com/?daddr=${encodedDest}&dirflg=${modeMap[mode] || 'd'}`;
|
|
46
|
+
} else {
|
|
47
|
+
// Google Maps on Android
|
|
48
|
+
const modeMap: Record<string, string> = {
|
|
49
|
+
driving: 'driving',
|
|
50
|
+
transit: 'transit',
|
|
51
|
+
walking: 'walking',
|
|
52
|
+
};
|
|
53
|
+
url = `google.navigation:q=${encodedDest}&mode=${modeMap[mode] || 'driving'}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const canOpen = await Linking.canOpenURL(url);
|
|
57
|
+
if (!canOpen) {
|
|
58
|
+
// Fallback to Google Maps web URL
|
|
59
|
+
const webModeMap: Record<string, string> = {
|
|
60
|
+
driving: 'driving',
|
|
61
|
+
transit: 'transit',
|
|
62
|
+
walking: 'walking',
|
|
63
|
+
};
|
|
64
|
+
url = `https://www.google.com/maps/dir/?api=1&destination=${encodedDest}&travelmode=${webModeMap[mode] || 'driving'}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await Linking.openURL(url);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
id: actionId,
|
|
71
|
+
ok: true,
|
|
72
|
+
result: {
|
|
73
|
+
message: `Navigation started to ${destination} via ${mode}`,
|
|
74
|
+
destination,
|
|
75
|
+
mode,
|
|
76
|
+
status: 'navigating',
|
|
77
|
+
},
|
|
78
|
+
executed_at: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
id: actionId,
|
|
83
|
+
ok: false,
|
|
84
|
+
error: `Navigation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
85
|
+
executed_at: new Date().toISOString(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Queue Poller — polls the relay for pending actions and executes them.
|
|
3
|
+
* Routes each action to the appropriate executor based on action type.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RelayClient } from '../relay-client';
|
|
7
|
+
import type { PendingAction, ActionResult, ActionType } from '../types';
|
|
8
|
+
import * as messaging from './messaging';
|
|
9
|
+
import * as calendarActions from './calendar-actions';
|
|
10
|
+
import * as dnd from './dnd';
|
|
11
|
+
import * as navigation from './navigation';
|
|
12
|
+
import * as healthActions from './health-actions';
|
|
13
|
+
|
|
14
|
+
export interface QueuePollerConfig {
|
|
15
|
+
/** Polling interval in ms (default: 5000) */
|
|
16
|
+
pollInterval?: number;
|
|
17
|
+
/** Relay client instance */
|
|
18
|
+
relayClient: RelayClient;
|
|
19
|
+
/** Callback when an action is executed */
|
|
20
|
+
onActionExecuted?: (result: ActionResult) => void;
|
|
21
|
+
/** Callback for errors */
|
|
22
|
+
onError?: (error: Error) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class QueuePoller {
|
|
26
|
+
private relayClient: RelayClient;
|
|
27
|
+
private pollInterval: number;
|
|
28
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
29
|
+
private running = false;
|
|
30
|
+
private onActionExecuted?: (result: ActionResult) => void;
|
|
31
|
+
private onError?: (error: Error) => void;
|
|
32
|
+
|
|
33
|
+
constructor(config: QueuePollerConfig) {
|
|
34
|
+
this.relayClient = config.relayClient;
|
|
35
|
+
this.pollInterval = config.pollInterval ?? 5_000;
|
|
36
|
+
this.onActionExecuted = config.onActionExecuted;
|
|
37
|
+
this.onError = config.onError;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Start polling for actions.
|
|
42
|
+
*/
|
|
43
|
+
start(): void {
|
|
44
|
+
if (this.running) return;
|
|
45
|
+
this.running = true;
|
|
46
|
+
|
|
47
|
+
// Poll immediately, then on interval
|
|
48
|
+
this.poll();
|
|
49
|
+
this.timer = setInterval(() => this.poll(), this.pollInterval);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Stop polling.
|
|
54
|
+
*/
|
|
55
|
+
stop(): void {
|
|
56
|
+
this.running = false;
|
|
57
|
+
if (this.timer) {
|
|
58
|
+
clearInterval(this.timer);
|
|
59
|
+
this.timer = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if poller is running.
|
|
65
|
+
*/
|
|
66
|
+
isRunning(): boolean {
|
|
67
|
+
return this.running;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Execute a single poll cycle.
|
|
72
|
+
*/
|
|
73
|
+
private async poll(): Promise<void> {
|
|
74
|
+
if (!this.running) return;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const response = await this.relayClient.pollActions();
|
|
78
|
+
if (!response.ok || !response.data) return;
|
|
79
|
+
|
|
80
|
+
for (const action of response.data) {
|
|
81
|
+
// The relay only ever returns pending actions — it filters in the
|
|
82
|
+
// query. A missing status therefore means "the server didn't say",
|
|
83
|
+
// not "already handled", and treating it as the latter discarded
|
|
84
|
+
// every queued action while every write tool still answered "queued".
|
|
85
|
+
// Only an explicitly non-pending status is skipped.
|
|
86
|
+
if (action.status && action.status !== 'pending') continue;
|
|
87
|
+
|
|
88
|
+
const result = await this.executeAction(action);
|
|
89
|
+
|
|
90
|
+
// Report result back to relay
|
|
91
|
+
await this.relayClient.completeAction(result);
|
|
92
|
+
|
|
93
|
+
this.onActionExecuted?.(result);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.onError?.(
|
|
97
|
+
error instanceof Error ? error : new Error(String(error))
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Route an action to the appropriate executor.
|
|
104
|
+
*/
|
|
105
|
+
private async executeAction(action: PendingAction): Promise<ActionResult> {
|
|
106
|
+
const { id, type, params } = action;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
switch (type as ActionType) {
|
|
110
|
+
case 'messaging.send':
|
|
111
|
+
return await messaging.execute(id, params);
|
|
112
|
+
|
|
113
|
+
case 'calendar.create':
|
|
114
|
+
return await calendarActions.executeCreate(id, params);
|
|
115
|
+
|
|
116
|
+
case 'calendar.reschedule':
|
|
117
|
+
return await calendarActions.executeReschedule(id, params);
|
|
118
|
+
|
|
119
|
+
case 'calendar.cancel':
|
|
120
|
+
return await calendarActions.executeCancel(id, params);
|
|
121
|
+
|
|
122
|
+
case 'dnd.set':
|
|
123
|
+
return await dnd.execute(id, params);
|
|
124
|
+
|
|
125
|
+
case 'navigation.start':
|
|
126
|
+
return await navigation.execute(id, params);
|
|
127
|
+
|
|
128
|
+
case 'health.today':
|
|
129
|
+
return await healthActions.executeToday(id);
|
|
130
|
+
|
|
131
|
+
case 'health.history':
|
|
132
|
+
return await healthActions.executeHistory(id, params);
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
return {
|
|
136
|
+
id,
|
|
137
|
+
ok: false,
|
|
138
|
+
error: `Unknown action type: ${type}`,
|
|
139
|
+
executed_at: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
id,
|
|
145
|
+
ok: false,
|
|
146
|
+
error: `Action execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
147
|
+
executed_at: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|