@elizaos/plugin-imessage 2.0.0-alpha.3
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/__tests__/integration.test.ts +548 -0
- package/build.ts +16 -0
- package/dist/index.js +46 -0
- package/package.json +33 -0
- package/src/accounts.ts +379 -0
- package/src/actions/index.ts +5 -0
- package/src/actions/sendMessage.ts +218 -0
- package/src/config.ts +82 -0
- package/src/index.ts +113 -0
- package/src/providers/chatContext.ts +86 -0
- package/src/providers/index.ts +5 -0
- package/src/rpc.ts +485 -0
- package/src/service.ts +589 -0
- package/src/types.ts +291 -0
- package/tsconfig.json +20 -0
package/src/accounts.ts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import type { IAgentRuntime } from "@elizaos/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default account identifier used when no specific account is configured
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_ACCOUNT_ID = "default";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Group-specific configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface IMessageGroupConfig {
|
|
12
|
+
/** If false, ignore messages from this group */
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
/** Allowlist for users in this group */
|
|
15
|
+
allowFrom?: Array<string | number>;
|
|
16
|
+
/** Require bot mention to respond */
|
|
17
|
+
requireMention?: boolean;
|
|
18
|
+
/** Custom system prompt for this group */
|
|
19
|
+
systemPrompt?: string;
|
|
20
|
+
/** Skills enabled for this group */
|
|
21
|
+
skills?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for a single iMessage account
|
|
26
|
+
*/
|
|
27
|
+
export interface IMessageAccountConfig {
|
|
28
|
+
/** Optional display name for this account */
|
|
29
|
+
name?: string;
|
|
30
|
+
/** If false, do not start this iMessage account */
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
/** Path to the iMessage CLI tool */
|
|
33
|
+
cliPath?: string;
|
|
34
|
+
/** Path to the iMessage database */
|
|
35
|
+
dbPath?: string;
|
|
36
|
+
/** iMessage service type (iMessage or SMS) */
|
|
37
|
+
service?: "iMessage" | "SMS";
|
|
38
|
+
/** Phone number region code */
|
|
39
|
+
region?: string;
|
|
40
|
+
/** Allowlist for DM senders */
|
|
41
|
+
allowFrom?: Array<string | number>;
|
|
42
|
+
/** Allowlist for groups */
|
|
43
|
+
groupAllowFrom?: Array<string | number>;
|
|
44
|
+
/** DM access policy */
|
|
45
|
+
dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
|
|
46
|
+
/** Group message access policy */
|
|
47
|
+
groupPolicy?: "open" | "allowlist" | "disabled";
|
|
48
|
+
/** Whether to include attachments */
|
|
49
|
+
includeAttachments?: boolean;
|
|
50
|
+
/** Max media size in MB */
|
|
51
|
+
mediaMaxMb?: number;
|
|
52
|
+
/** Text chunk limit for messages */
|
|
53
|
+
textChunkLimit?: number;
|
|
54
|
+
/** Group-specific configurations */
|
|
55
|
+
groups?: Record<string, IMessageGroupConfig>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Multi-account iMessage configuration structure
|
|
60
|
+
*/
|
|
61
|
+
export interface IMessageMultiAccountConfig {
|
|
62
|
+
/** Default/base configuration applied to all accounts */
|
|
63
|
+
enabled?: boolean;
|
|
64
|
+
cliPath?: string;
|
|
65
|
+
dbPath?: string;
|
|
66
|
+
service?: "iMessage" | "SMS";
|
|
67
|
+
region?: string;
|
|
68
|
+
dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
|
|
69
|
+
groupPolicy?: "open" | "allowlist" | "disabled";
|
|
70
|
+
includeAttachments?: boolean;
|
|
71
|
+
mediaMaxMb?: number;
|
|
72
|
+
textChunkLimit?: number;
|
|
73
|
+
/** Per-account configuration overrides */
|
|
74
|
+
accounts?: Record<string, IMessageAccountConfig>;
|
|
75
|
+
/** Group configurations at base level */
|
|
76
|
+
groups?: Record<string, IMessageGroupConfig>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolved iMessage account with all configuration merged
|
|
81
|
+
*/
|
|
82
|
+
export interface ResolvedIMessageAccount {
|
|
83
|
+
accountId: string;
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
name?: string;
|
|
86
|
+
cliPath: string;
|
|
87
|
+
dbPath?: string;
|
|
88
|
+
configured: boolean;
|
|
89
|
+
config: IMessageAccountConfig;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalizes an account ID, returning the default if not provided
|
|
94
|
+
*/
|
|
95
|
+
export function normalizeAccountId(accountId?: string | null): string {
|
|
96
|
+
if (!accountId || typeof accountId !== "string") {
|
|
97
|
+
return DEFAULT_ACCOUNT_ID;
|
|
98
|
+
}
|
|
99
|
+
const trimmed = accountId.trim().toLowerCase();
|
|
100
|
+
if (!trimmed || trimmed === "default") {
|
|
101
|
+
return DEFAULT_ACCOUNT_ID;
|
|
102
|
+
}
|
|
103
|
+
return trimmed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the multi-account configuration from runtime settings
|
|
108
|
+
*/
|
|
109
|
+
export function getMultiAccountConfig(
|
|
110
|
+
runtime: IAgentRuntime,
|
|
111
|
+
): IMessageMultiAccountConfig {
|
|
112
|
+
const characterIMessage = runtime.character?.settings?.imessage as
|
|
113
|
+
| IMessageMultiAccountConfig
|
|
114
|
+
| undefined;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
enabled: characterIMessage?.enabled,
|
|
118
|
+
cliPath: characterIMessage?.cliPath,
|
|
119
|
+
dbPath: characterIMessage?.dbPath,
|
|
120
|
+
service: characterIMessage?.service,
|
|
121
|
+
region: characterIMessage?.region,
|
|
122
|
+
dmPolicy: characterIMessage?.dmPolicy,
|
|
123
|
+
groupPolicy: characterIMessage?.groupPolicy,
|
|
124
|
+
includeAttachments: characterIMessage?.includeAttachments,
|
|
125
|
+
mediaMaxMb: characterIMessage?.mediaMaxMb,
|
|
126
|
+
textChunkLimit: characterIMessage?.textChunkLimit,
|
|
127
|
+
accounts: characterIMessage?.accounts,
|
|
128
|
+
groups: characterIMessage?.groups,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Lists all configured account IDs
|
|
134
|
+
*/
|
|
135
|
+
export function listIMessageAccountIds(runtime: IAgentRuntime): string[] {
|
|
136
|
+
const config = getMultiAccountConfig(runtime);
|
|
137
|
+
const accounts = config.accounts;
|
|
138
|
+
|
|
139
|
+
if (!accounts || typeof accounts !== "object") {
|
|
140
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const ids = Object.keys(accounts).filter(Boolean);
|
|
144
|
+
if (ids.length === 0) {
|
|
145
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return ids.slice().sort((a: string, b: string) => a.localeCompare(b));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolves the default account ID to use
|
|
153
|
+
*/
|
|
154
|
+
export function resolveDefaultIMessageAccountId(
|
|
155
|
+
runtime: IAgentRuntime,
|
|
156
|
+
): string {
|
|
157
|
+
const ids = listIMessageAccountIds(runtime);
|
|
158
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
159
|
+
return DEFAULT_ACCOUNT_ID;
|
|
160
|
+
}
|
|
161
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Gets the account-specific configuration
|
|
166
|
+
*/
|
|
167
|
+
function getAccountConfig(
|
|
168
|
+
runtime: IAgentRuntime,
|
|
169
|
+
accountId: string,
|
|
170
|
+
): IMessageAccountConfig | undefined {
|
|
171
|
+
const config = getMultiAccountConfig(runtime);
|
|
172
|
+
const accounts = config.accounts;
|
|
173
|
+
|
|
174
|
+
if (!accounts || typeof accounts !== "object") {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return accounts[accountId];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Merges base configuration with account-specific overrides
|
|
183
|
+
*/
|
|
184
|
+
function mergeIMessageAccountConfig(
|
|
185
|
+
runtime: IAgentRuntime,
|
|
186
|
+
accountId: string,
|
|
187
|
+
): IMessageAccountConfig {
|
|
188
|
+
const multiConfig = getMultiAccountConfig(runtime);
|
|
189
|
+
const { accounts: _ignored, ...baseConfig } = multiConfig;
|
|
190
|
+
const accountConfig = getAccountConfig(runtime, accountId) ?? {};
|
|
191
|
+
|
|
192
|
+
// Get environment/runtime settings for the base config
|
|
193
|
+
const envCliPath = runtime.getSetting("IMESSAGE_CLI_PATH") as
|
|
194
|
+
| string
|
|
195
|
+
| undefined;
|
|
196
|
+
const envDbPath = runtime.getSetting("IMESSAGE_DB_PATH") as
|
|
197
|
+
| string
|
|
198
|
+
| undefined;
|
|
199
|
+
const envDmPolicy = runtime.getSetting("IMESSAGE_DM_POLICY") as
|
|
200
|
+
| string
|
|
201
|
+
| undefined;
|
|
202
|
+
const envGroupPolicy = runtime.getSetting("IMESSAGE_GROUP_POLICY") as
|
|
203
|
+
| string
|
|
204
|
+
| undefined;
|
|
205
|
+
|
|
206
|
+
const envConfig: IMessageAccountConfig = {
|
|
207
|
+
cliPath: envCliPath || undefined,
|
|
208
|
+
dbPath: envDbPath || undefined,
|
|
209
|
+
dmPolicy: envDmPolicy as IMessageAccountConfig["dmPolicy"] | undefined,
|
|
210
|
+
groupPolicy: envGroupPolicy as
|
|
211
|
+
| IMessageAccountConfig["groupPolicy"]
|
|
212
|
+
| undefined,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Merge order: env defaults < base config < account config
|
|
216
|
+
return {
|
|
217
|
+
...envConfig,
|
|
218
|
+
...baseConfig,
|
|
219
|
+
...accountConfig,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Resolves a complete iMessage account configuration
|
|
225
|
+
*/
|
|
226
|
+
export function resolveIMessageAccount(
|
|
227
|
+
runtime: IAgentRuntime,
|
|
228
|
+
accountId?: string | null,
|
|
229
|
+
): ResolvedIMessageAccount {
|
|
230
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
231
|
+
const multiConfig = getMultiAccountConfig(runtime);
|
|
232
|
+
|
|
233
|
+
const baseEnabled = multiConfig.enabled !== false;
|
|
234
|
+
const merged = mergeIMessageAccountConfig(runtime, normalizedAccountId);
|
|
235
|
+
const accountEnabled = merged.enabled !== false;
|
|
236
|
+
const enabled = baseEnabled && accountEnabled;
|
|
237
|
+
|
|
238
|
+
const cliPath = merged.cliPath?.trim() || "imsg";
|
|
239
|
+
|
|
240
|
+
// Determine if this account is actually configured
|
|
241
|
+
const configured = Boolean(
|
|
242
|
+
merged.cliPath?.trim() ||
|
|
243
|
+
merged.dbPath?.trim() ||
|
|
244
|
+
merged.service ||
|
|
245
|
+
merged.region?.trim() ||
|
|
246
|
+
(merged.allowFrom && merged.allowFrom.length > 0) ||
|
|
247
|
+
(merged.groupAllowFrom && merged.groupAllowFrom.length > 0) ||
|
|
248
|
+
merged.dmPolicy ||
|
|
249
|
+
merged.groupPolicy ||
|
|
250
|
+
typeof merged.includeAttachments === "boolean" ||
|
|
251
|
+
typeof merged.mediaMaxMb === "number" ||
|
|
252
|
+
typeof merged.textChunkLimit === "number" ||
|
|
253
|
+
(merged.groups && Object.keys(merged.groups).length > 0),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
accountId: normalizedAccountId,
|
|
258
|
+
enabled,
|
|
259
|
+
name: merged.name?.trim() || undefined,
|
|
260
|
+
cliPath,
|
|
261
|
+
dbPath: merged.dbPath?.trim() || undefined,
|
|
262
|
+
configured,
|
|
263
|
+
config: merged,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Lists all enabled iMessage accounts
|
|
269
|
+
*/
|
|
270
|
+
export function listEnabledIMessageAccounts(
|
|
271
|
+
runtime: IAgentRuntime,
|
|
272
|
+
): ResolvedIMessageAccount[] {
|
|
273
|
+
return listIMessageAccountIds(runtime)
|
|
274
|
+
.map((accountId) => resolveIMessageAccount(runtime, accountId))
|
|
275
|
+
.filter((account) => account.enabled);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Checks if multi-account mode is enabled
|
|
280
|
+
*/
|
|
281
|
+
export function isMultiAccountEnabled(runtime: IAgentRuntime): boolean {
|
|
282
|
+
const accounts = listEnabledIMessageAccounts(runtime);
|
|
283
|
+
return accounts.length > 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Resolves group configuration for a specific group
|
|
288
|
+
*/
|
|
289
|
+
export function resolveIMessageGroupConfig(
|
|
290
|
+
runtime: IAgentRuntime,
|
|
291
|
+
accountId: string,
|
|
292
|
+
groupId: string,
|
|
293
|
+
): IMessageGroupConfig | undefined {
|
|
294
|
+
const multiConfig = getMultiAccountConfig(runtime);
|
|
295
|
+
const accountConfig = getAccountConfig(runtime, accountId);
|
|
296
|
+
|
|
297
|
+
// Check account-level groups first
|
|
298
|
+
const accountGroup = accountConfig?.groups?.[groupId];
|
|
299
|
+
if (accountGroup) {
|
|
300
|
+
return accountGroup;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Fall back to base-level groups
|
|
304
|
+
return multiConfig.groups?.[groupId];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Checks if a user is allowed based on policy and allowlist
|
|
309
|
+
*/
|
|
310
|
+
export function isIMessageUserAllowed(params: {
|
|
311
|
+
identifier: string;
|
|
312
|
+
accountConfig: IMessageAccountConfig;
|
|
313
|
+
isGroup: boolean;
|
|
314
|
+
groupId?: string;
|
|
315
|
+
groupConfig?: IMessageGroupConfig;
|
|
316
|
+
}): boolean {
|
|
317
|
+
const { identifier, accountConfig, isGroup, groupConfig } = params;
|
|
318
|
+
|
|
319
|
+
if (isGroup) {
|
|
320
|
+
const policy = accountConfig.groupPolicy ?? "allowlist";
|
|
321
|
+
if (policy === "disabled") {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (policy === "open") {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check group-specific allowlist first
|
|
330
|
+
if (groupConfig?.allowFrom?.length) {
|
|
331
|
+
return groupConfig.allowFrom.some(
|
|
332
|
+
(allowed) => String(allowed) === identifier,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check account-level group allowlist
|
|
337
|
+
if (accountConfig.groupAllowFrom?.length) {
|
|
338
|
+
return accountConfig.groupAllowFrom.some(
|
|
339
|
+
(allowed) => String(allowed) === identifier,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return policy !== "allowlist";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// DM handling
|
|
347
|
+
const policy = accountConfig.dmPolicy ?? "pairing";
|
|
348
|
+
if (policy === "disabled") {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (policy === "open") {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (policy === "pairing") {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Allowlist policy
|
|
361
|
+
if (accountConfig.allowFrom?.length) {
|
|
362
|
+
return accountConfig.allowFrom.some(
|
|
363
|
+
(allowed) => String(allowed) === identifier,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Checks if mention is required in a group
|
|
372
|
+
*/
|
|
373
|
+
export function isIMessageMentionRequired(params: {
|
|
374
|
+
accountConfig: IMessageAccountConfig;
|
|
375
|
+
groupConfig?: IMessageGroupConfig;
|
|
376
|
+
}): boolean {
|
|
377
|
+
const { groupConfig } = params;
|
|
378
|
+
return groupConfig?.requireMention ?? false;
|
|
379
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send message action for the iMessage plugin.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Action,
|
|
7
|
+
ActionResult,
|
|
8
|
+
HandlerCallback,
|
|
9
|
+
IAgentRuntime,
|
|
10
|
+
Memory,
|
|
11
|
+
State,
|
|
12
|
+
} from "@elizaos/core";
|
|
13
|
+
import {
|
|
14
|
+
composePromptFromState,
|
|
15
|
+
logger,
|
|
16
|
+
ModelType,
|
|
17
|
+
parseJSONObjectFromText,
|
|
18
|
+
} from "@elizaos/core";
|
|
19
|
+
import type { IMessageService } from "../service.js";
|
|
20
|
+
import {
|
|
21
|
+
IMESSAGE_SERVICE_NAME,
|
|
22
|
+
isValidIMessageTarget,
|
|
23
|
+
normalizeIMessageTarget,
|
|
24
|
+
} from "../types.js";
|
|
25
|
+
|
|
26
|
+
const SEND_MESSAGE_TEMPLATE = `# Task: Extract iMessage parameters
|
|
27
|
+
|
|
28
|
+
Based on the conversation, determine what message to send and to whom.
|
|
29
|
+
|
|
30
|
+
Recent conversation:
|
|
31
|
+
{{recentMessages}}
|
|
32
|
+
|
|
33
|
+
Extract the following:
|
|
34
|
+
1. text: The message content to send
|
|
35
|
+
2. to: The recipient (phone number, email, or "current" to reply)
|
|
36
|
+
|
|
37
|
+
Respond with a JSON object:
|
|
38
|
+
\`\`\`json
|
|
39
|
+
{
|
|
40
|
+
"text": "message to send",
|
|
41
|
+
"to": "phone/email or 'current'"
|
|
42
|
+
}
|
|
43
|
+
\`\`\`
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
interface SendMessageParams {
|
|
47
|
+
text: string;
|
|
48
|
+
to: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const sendMessage: Action = {
|
|
52
|
+
name: "IMESSAGE_SEND_MESSAGE",
|
|
53
|
+
similes: ["SEND_IMESSAGE", "IMESSAGE_TEXT", "TEXT_IMESSAGE", "SEND_IMSG"],
|
|
54
|
+
description: "Send a text message via iMessage (macOS only)",
|
|
55
|
+
|
|
56
|
+
validate: async (
|
|
57
|
+
_runtime: IAgentRuntime,
|
|
58
|
+
message: Memory,
|
|
59
|
+
_state?: State,
|
|
60
|
+
): Promise<boolean> => {
|
|
61
|
+
return message.content.source === "imessage";
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
handler: async (
|
|
65
|
+
runtime: IAgentRuntime,
|
|
66
|
+
message: Memory,
|
|
67
|
+
state: State | undefined,
|
|
68
|
+
_options?: Record<string, unknown>,
|
|
69
|
+
callback?: HandlerCallback,
|
|
70
|
+
): Promise<ActionResult> => {
|
|
71
|
+
const imessageService = runtime.getService<IMessageService>(
|
|
72
|
+
IMESSAGE_SERVICE_NAME,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (!imessageService || !imessageService.isConnected()) {
|
|
76
|
+
if (callback) {
|
|
77
|
+
callback({
|
|
78
|
+
text: "iMessage service is not available.",
|
|
79
|
+
source: "imessage",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return { success: false, error: "iMessage service not available" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!imessageService.isMacOS()) {
|
|
86
|
+
if (callback) {
|
|
87
|
+
callback({
|
|
88
|
+
text: "iMessage is only available on macOS.",
|
|
89
|
+
source: "imessage",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return { success: false, error: "iMessage requires macOS" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Compose state if not provided
|
|
96
|
+
const currentState = state ?? (await runtime.composeState(message));
|
|
97
|
+
|
|
98
|
+
// Extract parameters using LLM
|
|
99
|
+
const prompt = await composePromptFromState({
|
|
100
|
+
template: SEND_MESSAGE_TEMPLATE,
|
|
101
|
+
state: currentState,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
let msgInfo: SendMessageParams | null = null;
|
|
105
|
+
|
|
106
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
107
|
+
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
108
|
+
prompt,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const parsed = parseJSONObjectFromText(response);
|
|
112
|
+
if (parsed?.text) {
|
|
113
|
+
msgInfo = {
|
|
114
|
+
text: String(parsed.text),
|
|
115
|
+
to: String(parsed.to || "current"),
|
|
116
|
+
};
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!msgInfo || !msgInfo.text) {
|
|
122
|
+
if (callback) {
|
|
123
|
+
callback({
|
|
124
|
+
text: "I couldn't understand what message you want me to send. Please try again.",
|
|
125
|
+
source: "imessage",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return { success: false, error: "Could not extract message parameters" };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Determine target
|
|
132
|
+
let targetId: string | undefined;
|
|
133
|
+
|
|
134
|
+
if (msgInfo.to && msgInfo.to !== "current") {
|
|
135
|
+
const normalized = normalizeIMessageTarget(msgInfo.to);
|
|
136
|
+
if (normalized && isValidIMessageTarget(normalized)) {
|
|
137
|
+
targetId = normalized;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fall back to current chat
|
|
142
|
+
if (!targetId) {
|
|
143
|
+
const stateData = (currentState.data || {}) as Record<string, unknown>;
|
|
144
|
+
targetId = (stateData.chatId as string) || (stateData.handle as string);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!targetId) {
|
|
148
|
+
if (callback) {
|
|
149
|
+
callback({
|
|
150
|
+
text: "I couldn't determine who to send the message to. Please specify a phone number or email.",
|
|
151
|
+
source: "imessage",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { success: false, error: "Could not determine recipient" };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Send message
|
|
158
|
+
const result = await imessageService.sendMessage(targetId, msgInfo.text);
|
|
159
|
+
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
if (callback) {
|
|
162
|
+
callback({
|
|
163
|
+
text: `Failed to send message: ${result.error}`,
|
|
164
|
+
source: "imessage",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return { success: false, error: result.error };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
logger.debug(`Sent iMessage to ${targetId}`);
|
|
171
|
+
|
|
172
|
+
if (callback) {
|
|
173
|
+
callback({
|
|
174
|
+
text: "Message sent successfully.",
|
|
175
|
+
source: message.content.source as string,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
data: {
|
|
182
|
+
to: targetId,
|
|
183
|
+
messageId: result.messageId,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
examples: [
|
|
189
|
+
[
|
|
190
|
+
{
|
|
191
|
+
name: "{{user1}}",
|
|
192
|
+
content: { text: "Send them a message saying 'Hello!'" },
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "{{agent}}",
|
|
196
|
+
content: {
|
|
197
|
+
text: "I'll send that message via iMessage.",
|
|
198
|
+
actions: ["IMESSAGE_SEND_MESSAGE"],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
name: "{{user1}}",
|
|
205
|
+
content: {
|
|
206
|
+
text: "Text +1234567890 saying 'I'll be there in 10 minutes'",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "{{agent}}",
|
|
211
|
+
content: {
|
|
212
|
+
text: "I'll send that text.",
|
|
213
|
+
actions: ["IMESSAGE_SEND_MESSAGE"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
],
|
|
218
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iMessage plugin configuration types.
|
|
3
|
+
*
|
|
4
|
+
* These types define the configuration schema for the iMessage plugin.
|
|
5
|
+
* Shared base types are imported from @elizaos/core.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BlockStreamingCoalesceConfig,
|
|
10
|
+
ChannelHeartbeatVisibilityConfig,
|
|
11
|
+
DmConfig,
|
|
12
|
+
DmPolicy,
|
|
13
|
+
GroupPolicy,
|
|
14
|
+
MarkdownConfig,
|
|
15
|
+
} from "@elizaos/core";
|
|
16
|
+
|
|
17
|
+
// ============================================================
|
|
18
|
+
// Reaction Configuration
|
|
19
|
+
// ============================================================
|
|
20
|
+
|
|
21
|
+
export type IMessageReactionNotificationMode = "off" | "own" | "all" | "allowlist";
|
|
22
|
+
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Account Configuration
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
27
|
+
export type IMessageAccountConfig = {
|
|
28
|
+
/** Optional display name for this account (used in CLI/UI lists). */
|
|
29
|
+
name?: string;
|
|
30
|
+
/** Optional provider capability tags used for agent/runtime guidance. */
|
|
31
|
+
capabilities?: string[];
|
|
32
|
+
/** Markdown formatting overrides (tables). */
|
|
33
|
+
markdown?: MarkdownConfig;
|
|
34
|
+
/** Allow channel-initiated config writes (default: true). */
|
|
35
|
+
configWrites?: boolean;
|
|
36
|
+
/** If false, do not start this iMessage account. Default: true. */
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/** Direct message access policy (default: pairing). */
|
|
39
|
+
dmPolicy?: DmPolicy;
|
|
40
|
+
/** Optional allowlist for iMessage senders (phone E.164 or iCloud email). */
|
|
41
|
+
allowFrom?: Array<string | number>;
|
|
42
|
+
/** Optional allowlist for iMessage group senders. */
|
|
43
|
+
groupAllowFrom?: Array<string | number>;
|
|
44
|
+
/**
|
|
45
|
+
* Controls how group messages are handled:
|
|
46
|
+
* - "open": groups bypass allowFrom, no extra gating
|
|
47
|
+
* - "disabled": block all group messages
|
|
48
|
+
* - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
|
49
|
+
*/
|
|
50
|
+
groupPolicy?: GroupPolicy;
|
|
51
|
+
/** Max group messages to keep as history context (0 disables). */
|
|
52
|
+
historyLimit?: number;
|
|
53
|
+
/** Max DM turns to keep as history context. */
|
|
54
|
+
dmHistoryLimit?: number;
|
|
55
|
+
/** Per-DM config overrides keyed by user ID. */
|
|
56
|
+
dms?: Record<string, DmConfig>;
|
|
57
|
+
/** Outbound text chunk size (chars). Default: 4000. */
|
|
58
|
+
textChunkLimit?: number;
|
|
59
|
+
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
|
|
60
|
+
chunkMode?: "length" | "newline";
|
|
61
|
+
/** Disable block streaming for this account. */
|
|
62
|
+
blockStreaming?: boolean;
|
|
63
|
+
/** Merge streamed block replies before sending. */
|
|
64
|
+
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
|
65
|
+
/** Maximum media file size in MB. Default: 100. */
|
|
66
|
+
mediaMaxMb?: number;
|
|
67
|
+
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
|
68
|
+
reactionNotifications?: IMessageReactionNotificationMode;
|
|
69
|
+
/** Allowlist for reaction notifications when mode is allowlist. */
|
|
70
|
+
reactionAllowlist?: Array<string | number>;
|
|
71
|
+
/** Heartbeat visibility settings for this channel. */
|
|
72
|
+
heartbeat?: ChannelHeartbeatVisibilityConfig;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================================
|
|
76
|
+
// Main iMessage Configuration
|
|
77
|
+
// ============================================================
|
|
78
|
+
|
|
79
|
+
export type IMessageConfig = {
|
|
80
|
+
/** Optional per-account iMessage configuration (multi-account). */
|
|
81
|
+
accounts?: Record<string, IMessageAccountConfig>;
|
|
82
|
+
} & IMessageAccountConfig;
|