@elizaos/plugin-scheduling 2.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/index.d.ts +403 -0
- package/dist/index.js +1732 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1732 @@
|
|
|
1
|
+
// src/services/scheduling-service.ts
|
|
2
|
+
import { Service, logger } from "@elizaos/core";
|
|
3
|
+
import { v4 as uuidv42 } from "uuid";
|
|
4
|
+
|
|
5
|
+
// src/types.ts
|
|
6
|
+
var DEFAULT_CONFIG = {
|
|
7
|
+
defaultReminderMinutes: [1440, 120],
|
|
8
|
+
// 24 hours, 2 hours
|
|
9
|
+
maxProposals: 3,
|
|
10
|
+
defaultMaxDaysOut: 7,
|
|
11
|
+
minMeetingDuration: 30,
|
|
12
|
+
defaultMeetingDuration: 60,
|
|
13
|
+
autoSendCalendarInvites: true,
|
|
14
|
+
autoScheduleReminders: true
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/utils/ical.ts
|
|
18
|
+
var escapeIcs = (str) => {
|
|
19
|
+
return str.replace(/\\/g, "\\\\").replace(/;/g, "\\;").replace(/,/g, "\\,").replace(/\n/g, "\\n");
|
|
20
|
+
};
|
|
21
|
+
var formatIcsDate = (isoString) => {
|
|
22
|
+
return isoString.replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
23
|
+
};
|
|
24
|
+
var icsRole = (role) => {
|
|
25
|
+
switch (role) {
|
|
26
|
+
case "organizer":
|
|
27
|
+
return "REQ-PARTICIPANT";
|
|
28
|
+
case "required":
|
|
29
|
+
return "REQ-PARTICIPANT";
|
|
30
|
+
case "optional":
|
|
31
|
+
return "OPT-PARTICIPANT";
|
|
32
|
+
default:
|
|
33
|
+
return "REQ-PARTICIPANT";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var foldLine = (line) => {
|
|
37
|
+
const maxLength = 75;
|
|
38
|
+
if (line.length <= maxLength) {
|
|
39
|
+
return line;
|
|
40
|
+
}
|
|
41
|
+
const lines = [];
|
|
42
|
+
let remaining = line;
|
|
43
|
+
while (remaining.length > 0) {
|
|
44
|
+
if (lines.length === 0) {
|
|
45
|
+
lines.push(remaining.slice(0, maxLength));
|
|
46
|
+
remaining = remaining.slice(maxLength);
|
|
47
|
+
} else {
|
|
48
|
+
lines.push(" " + remaining.slice(0, maxLength - 1));
|
|
49
|
+
remaining = remaining.slice(maxLength - 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\r\n");
|
|
53
|
+
};
|
|
54
|
+
var generateIcs = (event) => {
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push("BEGIN:VCALENDAR");
|
|
57
|
+
lines.push("VERSION:2.0");
|
|
58
|
+
lines.push("PRODID:-//ElizaOS//SchedulingPlugin//EN");
|
|
59
|
+
lines.push("CALSCALE:GREGORIAN");
|
|
60
|
+
lines.push("METHOD:REQUEST");
|
|
61
|
+
lines.push("BEGIN:VEVENT");
|
|
62
|
+
lines.push(`UID:${event.uid}`);
|
|
63
|
+
lines.push(`DTSTAMP:${formatIcsDate((/* @__PURE__ */ new Date()).toISOString())}`);
|
|
64
|
+
lines.push(`DTSTART:${formatIcsDate(event.start)}`);
|
|
65
|
+
lines.push(`DTEND:${formatIcsDate(event.end)}`);
|
|
66
|
+
lines.push(`SUMMARY:${escapeIcs(event.title)}`);
|
|
67
|
+
if (event.description) {
|
|
68
|
+
lines.push(`DESCRIPTION:${escapeIcs(event.description)}`);
|
|
69
|
+
}
|
|
70
|
+
if (event.location) {
|
|
71
|
+
lines.push(`LOCATION:${escapeIcs(event.location)}`);
|
|
72
|
+
}
|
|
73
|
+
if (event.url) {
|
|
74
|
+
lines.push(`URL:${event.url}`);
|
|
75
|
+
}
|
|
76
|
+
if (event.organizer) {
|
|
77
|
+
lines.push(`ORGANIZER;CN=${escapeIcs(event.organizer.name)}:mailto:${event.organizer.email}`);
|
|
78
|
+
}
|
|
79
|
+
if (event.attendees) {
|
|
80
|
+
for (const attendee of event.attendees) {
|
|
81
|
+
const role = icsRole(attendee.role);
|
|
82
|
+
lines.push(
|
|
83
|
+
`ATTENDEE;ROLE=${role};PARTSTAT=NEEDS-ACTION;CN=${escapeIcs(attendee.name)}:mailto:${attendee.email}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (event.reminderMinutes) {
|
|
88
|
+
for (const minutes of event.reminderMinutes) {
|
|
89
|
+
lines.push("BEGIN:VALARM");
|
|
90
|
+
lines.push("ACTION:DISPLAY");
|
|
91
|
+
lines.push(`DESCRIPTION:${escapeIcs(event.title)}`);
|
|
92
|
+
lines.push(`TRIGGER:-PT${minutes}M`);
|
|
93
|
+
lines.push("END:VALARM");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
lines.push("STATUS:CONFIRMED");
|
|
97
|
+
lines.push("SEQUENCE:0");
|
|
98
|
+
lines.push("END:VEVENT");
|
|
99
|
+
lines.push("END:VCALENDAR");
|
|
100
|
+
return lines.map(foldLine).join("\r\n");
|
|
101
|
+
};
|
|
102
|
+
var parseIcs = (ics) => {
|
|
103
|
+
const events = [];
|
|
104
|
+
const lines = ics.split(/\r?\n/);
|
|
105
|
+
let currentEvent = null;
|
|
106
|
+
let currentLine = "";
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
109
|
+
currentLine += line.slice(1);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (currentLine && currentEvent) {
|
|
113
|
+
processLine(currentLine, currentEvent);
|
|
114
|
+
}
|
|
115
|
+
currentLine = line;
|
|
116
|
+
if (line === "BEGIN:VEVENT") {
|
|
117
|
+
currentEvent = {};
|
|
118
|
+
} else if (line === "END:VEVENT" && currentEvent) {
|
|
119
|
+
if (currentEvent.uid && currentEvent.start && currentEvent.end && currentEvent.title) {
|
|
120
|
+
events.push(currentEvent);
|
|
121
|
+
}
|
|
122
|
+
currentEvent = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return events;
|
|
126
|
+
};
|
|
127
|
+
var processLine = (line, event) => {
|
|
128
|
+
const colonIndex = line.indexOf(":");
|
|
129
|
+
if (colonIndex === -1) return;
|
|
130
|
+
const keyPart = line.slice(0, colonIndex);
|
|
131
|
+
const value = line.slice(colonIndex + 1);
|
|
132
|
+
const semiIndex = keyPart.indexOf(";");
|
|
133
|
+
const key = semiIndex === -1 ? keyPart : keyPart.slice(0, semiIndex);
|
|
134
|
+
switch (key) {
|
|
135
|
+
case "UID":
|
|
136
|
+
event.uid = value;
|
|
137
|
+
break;
|
|
138
|
+
case "SUMMARY":
|
|
139
|
+
event.title = unescapeIcs(value);
|
|
140
|
+
break;
|
|
141
|
+
case "DESCRIPTION":
|
|
142
|
+
event.description = unescapeIcs(value);
|
|
143
|
+
break;
|
|
144
|
+
case "DTSTART":
|
|
145
|
+
event.start = parseIcsDate(value);
|
|
146
|
+
break;
|
|
147
|
+
case "DTEND":
|
|
148
|
+
event.end = parseIcsDate(value);
|
|
149
|
+
break;
|
|
150
|
+
case "LOCATION":
|
|
151
|
+
event.location = unescapeIcs(value);
|
|
152
|
+
break;
|
|
153
|
+
case "URL":
|
|
154
|
+
event.url = value;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var unescapeIcs = (str) => {
|
|
159
|
+
return str.replace(/\\n/g, "\n").replace(/\\,/g, ",").replace(/\\;/g, ";").replace(/\\\\/g, "\\");
|
|
160
|
+
};
|
|
161
|
+
var parseIcsDate = (icsDate) => {
|
|
162
|
+
const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z?$/.exec(icsDate);
|
|
163
|
+
if (match) {
|
|
164
|
+
const [, year, month, day, hour, minute, second] = match;
|
|
165
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
|
|
166
|
+
}
|
|
167
|
+
return icsDate;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/storage.ts
|
|
171
|
+
import { v4 as uuidv4 } from "uuid";
|
|
172
|
+
var SCHEDULING_AVAILABILITY = "scheduling_availability";
|
|
173
|
+
var SCHEDULING_MEETING = "scheduling_meeting";
|
|
174
|
+
var SCHEDULING_REMINDER = "scheduling_reminder";
|
|
175
|
+
var SCHEDULING_REQUEST = "scheduling_request";
|
|
176
|
+
var SCHEDULING_MEETING_INDEX_ROOM = "scheduling_meeting_index:room";
|
|
177
|
+
var SCHEDULING_MEETING_INDEX_PARTICIPANT = "scheduling_meeting_index:participant";
|
|
178
|
+
var SCHEDULING_REMINDER_INDEX_MEETING = "scheduling_reminder_index:meeting";
|
|
179
|
+
async function getMeetingIndex(runtime, indexType, indexKey) {
|
|
180
|
+
const componentType = `${indexType}:${indexKey}`;
|
|
181
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
182
|
+
if (!component) {
|
|
183
|
+
return { meetingIds: [] };
|
|
184
|
+
}
|
|
185
|
+
return component.data;
|
|
186
|
+
}
|
|
187
|
+
async function saveMeetingIndex(runtime, indexType, indexKey, index) {
|
|
188
|
+
const componentType = `${indexType}:${indexKey}`;
|
|
189
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
190
|
+
const component = {
|
|
191
|
+
id: existing?.id || uuidv4(),
|
|
192
|
+
entityId: runtime.agentId,
|
|
193
|
+
agentId: runtime.agentId,
|
|
194
|
+
roomId: uuidv4(),
|
|
195
|
+
worldId: existing?.worldId || uuidv4(),
|
|
196
|
+
sourceEntityId: runtime.agentId,
|
|
197
|
+
type: componentType,
|
|
198
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
199
|
+
data: index
|
|
200
|
+
};
|
|
201
|
+
if (existing) {
|
|
202
|
+
await runtime.updateComponent(component);
|
|
203
|
+
} else {
|
|
204
|
+
await runtime.createComponent(component);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function addToMeetingIndex(runtime, indexType, indexKey, meetingId) {
|
|
208
|
+
const index = await getMeetingIndex(runtime, indexType, indexKey);
|
|
209
|
+
if (!index.meetingIds.includes(meetingId)) {
|
|
210
|
+
index.meetingIds.push(meetingId);
|
|
211
|
+
await saveMeetingIndex(runtime, indexType, indexKey, index);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function removeFromMeetingIndex(runtime, indexType, indexKey, meetingId) {
|
|
215
|
+
const index = await getMeetingIndex(runtime, indexType, indexKey);
|
|
216
|
+
index.meetingIds = index.meetingIds.filter((id) => id !== meetingId);
|
|
217
|
+
await saveMeetingIndex(runtime, indexType, indexKey, index);
|
|
218
|
+
}
|
|
219
|
+
async function getReminderIndex(runtime, meetingId) {
|
|
220
|
+
const componentType = `${SCHEDULING_REMINDER_INDEX_MEETING}:${meetingId}`;
|
|
221
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
222
|
+
return component ? component.data : { reminderIds: [] };
|
|
223
|
+
}
|
|
224
|
+
async function saveReminderIndex(runtime, meetingId, index) {
|
|
225
|
+
const componentType = `${SCHEDULING_REMINDER_INDEX_MEETING}:${meetingId}`;
|
|
226
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
227
|
+
const component = {
|
|
228
|
+
id: existing?.id || uuidv4(),
|
|
229
|
+
entityId: runtime.agentId,
|
|
230
|
+
agentId: runtime.agentId,
|
|
231
|
+
roomId: uuidv4(),
|
|
232
|
+
worldId: existing?.worldId || uuidv4(),
|
|
233
|
+
sourceEntityId: runtime.agentId,
|
|
234
|
+
type: componentType,
|
|
235
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
236
|
+
data: index
|
|
237
|
+
};
|
|
238
|
+
if (existing) {
|
|
239
|
+
await runtime.updateComponent(component);
|
|
240
|
+
} else {
|
|
241
|
+
await runtime.createComponent(component);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
var getAvailabilityStorage = (runtime) => ({
|
|
245
|
+
get: async (entityId) => {
|
|
246
|
+
const componentType = `${SCHEDULING_AVAILABILITY}:${entityId}`;
|
|
247
|
+
const component = await runtime.getComponent(entityId, componentType);
|
|
248
|
+
if (!component) return null;
|
|
249
|
+
return component.data;
|
|
250
|
+
},
|
|
251
|
+
save: async (entityId, availability) => {
|
|
252
|
+
const componentType = `${SCHEDULING_AVAILABILITY}:${entityId}`;
|
|
253
|
+
const existing = await runtime.getComponent(entityId, componentType);
|
|
254
|
+
const component = {
|
|
255
|
+
id: existing?.id || uuidv4(),
|
|
256
|
+
entityId,
|
|
257
|
+
agentId: runtime.agentId,
|
|
258
|
+
roomId: uuidv4(),
|
|
259
|
+
worldId: existing?.worldId || uuidv4(),
|
|
260
|
+
sourceEntityId: runtime.agentId,
|
|
261
|
+
type: componentType,
|
|
262
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
263
|
+
data: availability
|
|
264
|
+
};
|
|
265
|
+
if (existing) {
|
|
266
|
+
await runtime.updateComponent(component);
|
|
267
|
+
} else {
|
|
268
|
+
await runtime.createComponent(component);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
delete: async (entityId) => {
|
|
272
|
+
const componentType = `${SCHEDULING_AVAILABILITY}:${entityId}`;
|
|
273
|
+
const existing = await runtime.getComponent(entityId, componentType);
|
|
274
|
+
if (existing) {
|
|
275
|
+
await runtime.deleteComponent(existing.id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
var getSchedulingRequestStorage = (runtime) => ({
|
|
280
|
+
get: async (requestId) => {
|
|
281
|
+
const componentType = `${SCHEDULING_REQUEST}:${requestId}`;
|
|
282
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
283
|
+
if (!component) return null;
|
|
284
|
+
return component.data;
|
|
285
|
+
},
|
|
286
|
+
save: async (request) => {
|
|
287
|
+
const componentType = `${SCHEDULING_REQUEST}:${request.id}`;
|
|
288
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
289
|
+
const component = {
|
|
290
|
+
id: existing?.id || uuidv4(),
|
|
291
|
+
entityId: runtime.agentId,
|
|
292
|
+
agentId: runtime.agentId,
|
|
293
|
+
roomId: request.roomId,
|
|
294
|
+
worldId: existing?.worldId || uuidv4(),
|
|
295
|
+
sourceEntityId: runtime.agentId,
|
|
296
|
+
type: componentType,
|
|
297
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
298
|
+
data: request
|
|
299
|
+
};
|
|
300
|
+
if (existing) {
|
|
301
|
+
await runtime.updateComponent(component);
|
|
302
|
+
} else {
|
|
303
|
+
await runtime.createComponent(component);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
delete: async (requestId) => {
|
|
307
|
+
const componentType = `${SCHEDULING_REQUEST}:${requestId}`;
|
|
308
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
309
|
+
if (existing) {
|
|
310
|
+
await runtime.deleteComponent(existing.id);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
getByRoom: async (roomId) => {
|
|
314
|
+
const components = await runtime.getComponents(runtime.agentId);
|
|
315
|
+
const requests = [];
|
|
316
|
+
for (const component of components) {
|
|
317
|
+
if (component.type.startsWith(`${SCHEDULING_REQUEST}:`)) {
|
|
318
|
+
const request = component.data;
|
|
319
|
+
if (request.roomId === roomId) requests.push(request);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return requests;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
var getMeetingStorage = (runtime) => ({
|
|
326
|
+
get: async (meetingId) => {
|
|
327
|
+
const componentType = `${SCHEDULING_MEETING}:${meetingId}`;
|
|
328
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
329
|
+
if (!component) return null;
|
|
330
|
+
return component.data;
|
|
331
|
+
},
|
|
332
|
+
save: async (meeting) => {
|
|
333
|
+
const componentType = `${SCHEDULING_MEETING}:${meeting.id}`;
|
|
334
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
335
|
+
const component = {
|
|
336
|
+
id: existing?.id || uuidv4(),
|
|
337
|
+
entityId: runtime.agentId,
|
|
338
|
+
agentId: runtime.agentId,
|
|
339
|
+
roomId: meeting.roomId,
|
|
340
|
+
worldId: existing?.worldId || uuidv4(),
|
|
341
|
+
sourceEntityId: runtime.agentId,
|
|
342
|
+
type: componentType,
|
|
343
|
+
createdAt: existing?.createdAt || meeting.createdAt,
|
|
344
|
+
data: meeting
|
|
345
|
+
};
|
|
346
|
+
if (existing) {
|
|
347
|
+
await runtime.updateComponent(component);
|
|
348
|
+
} else {
|
|
349
|
+
await runtime.createComponent(component);
|
|
350
|
+
await addToMeetingIndex(runtime, SCHEDULING_MEETING_INDEX_ROOM, meeting.roomId, meeting.id);
|
|
351
|
+
for (const participant of meeting.participants) {
|
|
352
|
+
await addToMeetingIndex(
|
|
353
|
+
runtime,
|
|
354
|
+
SCHEDULING_MEETING_INDEX_PARTICIPANT,
|
|
355
|
+
participant.entityId,
|
|
356
|
+
meeting.id
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
delete: async (meetingId) => {
|
|
362
|
+
const componentType = `${SCHEDULING_MEETING}:${meetingId}`;
|
|
363
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
364
|
+
if (existing) {
|
|
365
|
+
const meeting = existing.data;
|
|
366
|
+
await removeFromMeetingIndex(runtime, SCHEDULING_MEETING_INDEX_ROOM, meeting.roomId, meetingId);
|
|
367
|
+
for (const participant of meeting.participants) {
|
|
368
|
+
await removeFromMeetingIndex(
|
|
369
|
+
runtime,
|
|
370
|
+
SCHEDULING_MEETING_INDEX_PARTICIPANT,
|
|
371
|
+
participant.entityId,
|
|
372
|
+
meetingId
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
await runtime.deleteComponent(existing.id);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
getByRoom: async (roomId) => {
|
|
379
|
+
const index = await getMeetingIndex(runtime, SCHEDULING_MEETING_INDEX_ROOM, roomId);
|
|
380
|
+
const meetings = [];
|
|
381
|
+
for (const meetingId of index.meetingIds) {
|
|
382
|
+
const componentType = `${SCHEDULING_MEETING}:${meetingId}`;
|
|
383
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
384
|
+
if (component) {
|
|
385
|
+
meetings.push(component.data);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return meetings;
|
|
389
|
+
},
|
|
390
|
+
getUpcomingForParticipant: async (entityId) => {
|
|
391
|
+
const index = await getMeetingIndex(runtime, SCHEDULING_MEETING_INDEX_PARTICIPANT, entityId);
|
|
392
|
+
const meetings = [];
|
|
393
|
+
const now = Date.now();
|
|
394
|
+
for (const meetingId of index.meetingIds) {
|
|
395
|
+
const componentType = `${SCHEDULING_MEETING}:${meetingId}`;
|
|
396
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
397
|
+
if (component) {
|
|
398
|
+
const meeting = component.data;
|
|
399
|
+
if (new Date(meeting.slot.start).getTime() > now && meeting.status !== "cancelled") {
|
|
400
|
+
meetings.push(meeting);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
meetings.sort((a, b) => new Date(a.slot.start).getTime() - new Date(b.slot.start).getTime());
|
|
405
|
+
return meetings;
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
var REMINDER_REGISTRY = "scheduling_reminder_registry";
|
|
409
|
+
async function getReminderRegistry(runtime) {
|
|
410
|
+
const component = await runtime.getComponent(runtime.agentId, REMINDER_REGISTRY);
|
|
411
|
+
if (!component) {
|
|
412
|
+
return { reminderIds: [] };
|
|
413
|
+
}
|
|
414
|
+
return component.data;
|
|
415
|
+
}
|
|
416
|
+
async function saveReminderRegistry(runtime, registry) {
|
|
417
|
+
const existing = await runtime.getComponent(runtime.agentId, REMINDER_REGISTRY);
|
|
418
|
+
const component = {
|
|
419
|
+
id: existing?.id || uuidv4(),
|
|
420
|
+
entityId: runtime.agentId,
|
|
421
|
+
agentId: runtime.agentId,
|
|
422
|
+
roomId: uuidv4(),
|
|
423
|
+
worldId: existing?.worldId || uuidv4(),
|
|
424
|
+
sourceEntityId: runtime.agentId,
|
|
425
|
+
type: REMINDER_REGISTRY,
|
|
426
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
427
|
+
data: registry
|
|
428
|
+
};
|
|
429
|
+
if (existing) {
|
|
430
|
+
await runtime.updateComponent(component);
|
|
431
|
+
} else {
|
|
432
|
+
await runtime.createComponent(component);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
var getReminderStorage = (runtime) => ({
|
|
436
|
+
get: async (reminderId) => {
|
|
437
|
+
const componentType = `${SCHEDULING_REMINDER}:${reminderId}`;
|
|
438
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
439
|
+
if (!component) return null;
|
|
440
|
+
return component.data;
|
|
441
|
+
},
|
|
442
|
+
save: async (reminder) => {
|
|
443
|
+
const componentType = `${SCHEDULING_REMINDER}:${reminder.id}`;
|
|
444
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
445
|
+
const component = {
|
|
446
|
+
id: existing?.id || uuidv4(),
|
|
447
|
+
entityId: runtime.agentId,
|
|
448
|
+
agentId: runtime.agentId,
|
|
449
|
+
roomId: uuidv4(),
|
|
450
|
+
worldId: existing?.worldId || uuidv4(),
|
|
451
|
+
sourceEntityId: runtime.agentId,
|
|
452
|
+
type: componentType,
|
|
453
|
+
createdAt: existing?.createdAt || reminder.createdAt,
|
|
454
|
+
data: reminder
|
|
455
|
+
};
|
|
456
|
+
if (existing) {
|
|
457
|
+
await runtime.updateComponent(component);
|
|
458
|
+
} else {
|
|
459
|
+
await runtime.createComponent(component);
|
|
460
|
+
const index = await getReminderIndex(runtime, reminder.meetingId);
|
|
461
|
+
if (!index.reminderIds.includes(reminder.id)) {
|
|
462
|
+
index.reminderIds.push(reminder.id);
|
|
463
|
+
await saveReminderIndex(runtime, reminder.meetingId, index);
|
|
464
|
+
}
|
|
465
|
+
const registry = await getReminderRegistry(runtime);
|
|
466
|
+
if (!registry.reminderIds.includes(reminder.id)) {
|
|
467
|
+
registry.reminderIds.push(reminder.id);
|
|
468
|
+
await saveReminderRegistry(runtime, registry);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
delete: async (reminderId) => {
|
|
473
|
+
const componentType = `${SCHEDULING_REMINDER}:${reminderId}`;
|
|
474
|
+
const existing = await runtime.getComponent(runtime.agentId, componentType);
|
|
475
|
+
if (existing) {
|
|
476
|
+
const reminder = existing.data;
|
|
477
|
+
const index = await getReminderIndex(runtime, reminder.meetingId);
|
|
478
|
+
index.reminderIds = index.reminderIds.filter((id) => id !== reminderId);
|
|
479
|
+
await saveReminderIndex(runtime, reminder.meetingId, index);
|
|
480
|
+
const registry = await getReminderRegistry(runtime);
|
|
481
|
+
registry.reminderIds = registry.reminderIds.filter((id) => id !== reminderId);
|
|
482
|
+
await saveReminderRegistry(runtime, registry);
|
|
483
|
+
await runtime.deleteComponent(existing.id);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
getByMeeting: async (meetingId) => {
|
|
487
|
+
const index = await getReminderIndex(runtime, meetingId);
|
|
488
|
+
const reminders = [];
|
|
489
|
+
for (const reminderId of index.reminderIds) {
|
|
490
|
+
const componentType = `${SCHEDULING_REMINDER}:${reminderId}`;
|
|
491
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
492
|
+
if (component) {
|
|
493
|
+
reminders.push(component.data);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return reminders;
|
|
497
|
+
},
|
|
498
|
+
getDue: async () => {
|
|
499
|
+
const registry = await getReminderRegistry(runtime);
|
|
500
|
+
const now = Date.now();
|
|
501
|
+
const dueReminders = [];
|
|
502
|
+
for (const reminderId of registry.reminderIds) {
|
|
503
|
+
const componentType = `${SCHEDULING_REMINDER}:${reminderId}`;
|
|
504
|
+
const component = await runtime.getComponent(runtime.agentId, componentType);
|
|
505
|
+
if (component) {
|
|
506
|
+
const reminder = component.data;
|
|
507
|
+
if (reminder.status === "pending" && new Date(reminder.scheduledFor).getTime() <= now) {
|
|
508
|
+
dueReminders.push(reminder);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return dueReminders;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// src/services/scheduling-service.ts
|
|
517
|
+
var DAY_INDEX = {
|
|
518
|
+
Mon: "mon",
|
|
519
|
+
Tue: "tue",
|
|
520
|
+
Wed: "wed",
|
|
521
|
+
Thu: "thu",
|
|
522
|
+
Fri: "fri",
|
|
523
|
+
Sat: "sat",
|
|
524
|
+
Sun: "sun"
|
|
525
|
+
};
|
|
526
|
+
var getZonedParts = (date, timeZone) => {
|
|
527
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
528
|
+
timeZone,
|
|
529
|
+
year: "numeric",
|
|
530
|
+
month: "2-digit",
|
|
531
|
+
day: "2-digit",
|
|
532
|
+
weekday: "short",
|
|
533
|
+
hour: "2-digit",
|
|
534
|
+
minute: "2-digit",
|
|
535
|
+
hour12: false
|
|
536
|
+
});
|
|
537
|
+
const parts = formatter.formatToParts(date);
|
|
538
|
+
const out = {};
|
|
539
|
+
for (const part of parts) {
|
|
540
|
+
if (part.type !== "literal") {
|
|
541
|
+
out[part.type] = part.value;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return out;
|
|
545
|
+
};
|
|
546
|
+
var getDayOfWeek = (date, timeZone) => {
|
|
547
|
+
const parts = getZonedParts(date, timeZone);
|
|
548
|
+
return DAY_INDEX[parts.weekday] ?? "mon";
|
|
549
|
+
};
|
|
550
|
+
var getMinutesOfDay = (date, timeZone) => {
|
|
551
|
+
const parts = getZonedParts(date, timeZone);
|
|
552
|
+
return Number(parts.hour) * 60 + Number(parts.minute);
|
|
553
|
+
};
|
|
554
|
+
var getDateString = (date, timeZone) => {
|
|
555
|
+
const parts = getZonedParts(date, timeZone);
|
|
556
|
+
return `${parts.year}-${parts.month}-${parts.day}`;
|
|
557
|
+
};
|
|
558
|
+
var dateFromMinutes = (dateStr, minutes, timeZone) => {
|
|
559
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
560
|
+
const hours = Math.floor(minutes / 60);
|
|
561
|
+
const mins = minutes % 60;
|
|
562
|
+
const localDateStr = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}:00`;
|
|
563
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
564
|
+
timeZone,
|
|
565
|
+
timeZoneName: "longOffset"
|
|
566
|
+
});
|
|
567
|
+
const tempDate = /* @__PURE__ */ new Date(localDateStr + "Z");
|
|
568
|
+
const offsetParts = formatter.formatToParts(tempDate);
|
|
569
|
+
const offsetPart = offsetParts.find((p) => p.type === "timeZoneName");
|
|
570
|
+
let offsetMinutes = 0;
|
|
571
|
+
if (offsetPart) {
|
|
572
|
+
const match = /GMT([+-])(\d{2}):(\d{2})/.exec(offsetPart.value);
|
|
573
|
+
if (match) {
|
|
574
|
+
const sign = match[1] === "+" ? 1 : -1;
|
|
575
|
+
offsetMinutes = sign * (Number(match[2]) * 60 + Number(match[3]));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const utcMs = Date.UTC(year, month - 1, day, hours, mins, 0, 0) - offsetMinutes * 60 * 1e3;
|
|
579
|
+
return new Date(utcMs);
|
|
580
|
+
};
|
|
581
|
+
var addDays = (date, days) => {
|
|
582
|
+
const result = new Date(date);
|
|
583
|
+
result.setDate(result.getDate() + days);
|
|
584
|
+
return result;
|
|
585
|
+
};
|
|
586
|
+
var SchedulingService = class _SchedulingService extends Service {
|
|
587
|
+
static serviceType = "SCHEDULING";
|
|
588
|
+
capabilityDescription = "Coordinates scheduling and calendar management across multiple participants";
|
|
589
|
+
schedulingConfig;
|
|
590
|
+
constructor(runtime, schedulingConfig) {
|
|
591
|
+
super(runtime);
|
|
592
|
+
this.schedulingConfig = { ...DEFAULT_CONFIG, ...schedulingConfig };
|
|
593
|
+
}
|
|
594
|
+
static async start(runtime) {
|
|
595
|
+
const service = new _SchedulingService(runtime);
|
|
596
|
+
const healthStatus = await service.healthCheck();
|
|
597
|
+
if (!healthStatus.healthy) {
|
|
598
|
+
logger.warn(`[SchedulingService] Started with warnings: ${healthStatus.issues.join(", ")}`);
|
|
599
|
+
} else {
|
|
600
|
+
logger.info(`[SchedulingService] Started for agent ${runtime.agentId}`);
|
|
601
|
+
}
|
|
602
|
+
return service;
|
|
603
|
+
}
|
|
604
|
+
async stop() {
|
|
605
|
+
logger.info("[SchedulingService] Stopped");
|
|
606
|
+
}
|
|
607
|
+
async healthCheck() {
|
|
608
|
+
const issues = [];
|
|
609
|
+
if (!this.runtime.getComponent) {
|
|
610
|
+
issues.push("Runtime missing getComponent method");
|
|
611
|
+
}
|
|
612
|
+
if (!this.runtime.createComponent) {
|
|
613
|
+
issues.push("Runtime missing createComponent method");
|
|
614
|
+
}
|
|
615
|
+
const emailService = this.runtime.getService("EMAIL");
|
|
616
|
+
if (!emailService) {
|
|
617
|
+
issues.push("EMAIL service not available");
|
|
618
|
+
}
|
|
619
|
+
return { healthy: issues.length === 0, issues };
|
|
620
|
+
}
|
|
621
|
+
getSchedulingConfig() {
|
|
622
|
+
return { ...this.schedulingConfig };
|
|
623
|
+
}
|
|
624
|
+
async saveAvailability(entityId, availability) {
|
|
625
|
+
const storage = getAvailabilityStorage(this.runtime);
|
|
626
|
+
await storage.save(entityId, availability);
|
|
627
|
+
}
|
|
628
|
+
async getAvailability(entityId) {
|
|
629
|
+
return getAvailabilityStorage(this.runtime).get(entityId);
|
|
630
|
+
}
|
|
631
|
+
isAvailableAt(availability, dateTime) {
|
|
632
|
+
const day = getDayOfWeek(dateTime, availability.timeZone);
|
|
633
|
+
const minutes = getMinutesOfDay(dateTime, availability.timeZone);
|
|
634
|
+
const dateStr = getDateString(dateTime, availability.timeZone);
|
|
635
|
+
const exception = availability.exceptions.find((e) => e.date === dateStr);
|
|
636
|
+
if (exception) {
|
|
637
|
+
if (exception.unavailable) return false;
|
|
638
|
+
if (exception.startMinutes !== void 0 && exception.endMinutes !== void 0) {
|
|
639
|
+
return minutes >= exception.startMinutes && minutes < exception.endMinutes;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return availability.weekly.some(
|
|
643
|
+
(window) => window.day === day && minutes >= window.startMinutes && minutes < window.endMinutes
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
async createSchedulingRequest(roomId, title, participants, constraints = {}, options) {
|
|
647
|
+
const request = {
|
|
648
|
+
id: uuidv42(),
|
|
649
|
+
roomId,
|
|
650
|
+
title,
|
|
651
|
+
description: options?.description,
|
|
652
|
+
participants,
|
|
653
|
+
constraints: {
|
|
654
|
+
minDurationMinutes: constraints.minDurationMinutes ?? this.schedulingConfig.minMeetingDuration,
|
|
655
|
+
preferredDurationMinutes: constraints.preferredDurationMinutes ?? this.schedulingConfig.defaultMeetingDuration,
|
|
656
|
+
maxDaysOut: constraints.maxDaysOut ?? this.schedulingConfig.defaultMaxDaysOut,
|
|
657
|
+
preferredTimes: constraints.preferredTimes,
|
|
658
|
+
preferredDays: constraints.preferredDays,
|
|
659
|
+
locationType: constraints.locationType,
|
|
660
|
+
locationConstraint: constraints.locationConstraint
|
|
661
|
+
},
|
|
662
|
+
urgency: options?.urgency ?? "flexible",
|
|
663
|
+
createdAt: Date.now(),
|
|
664
|
+
maxProposals: this.schedulingConfig.maxProposals
|
|
665
|
+
};
|
|
666
|
+
const storage = getSchedulingRequestStorage(this.runtime);
|
|
667
|
+
await storage.save(request);
|
|
668
|
+
logger.info(`[SchedulingService] Created scheduling request ${request.id} for "${title}"`);
|
|
669
|
+
return request;
|
|
670
|
+
}
|
|
671
|
+
async findAvailableSlots(request) {
|
|
672
|
+
const { participants, constraints } = request;
|
|
673
|
+
if (participants.length === 0) {
|
|
674
|
+
return {
|
|
675
|
+
success: false,
|
|
676
|
+
proposedSlots: [],
|
|
677
|
+
failureReason: "No participants specified"
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const availabilities = participants.map((p) => ({
|
|
681
|
+
participant: p,
|
|
682
|
+
availability: p.availability
|
|
683
|
+
}));
|
|
684
|
+
const referenceTimeZone = availabilities[0].availability.timeZone;
|
|
685
|
+
const now = /* @__PURE__ */ new Date();
|
|
686
|
+
const candidateSlots = [];
|
|
687
|
+
for (let dayOffset = 0; dayOffset < constraints.maxDaysOut; dayOffset++) {
|
|
688
|
+
const date = addDays(now, dayOffset);
|
|
689
|
+
const day = getDayOfWeek(date, referenceTimeZone);
|
|
690
|
+
if (constraints.preferredDays && !constraints.preferredDays.includes(day)) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const dateStr = getDateString(date, referenceTimeZone);
|
|
694
|
+
const dayWindows = this.findDayIntersection(availabilities, day, dateStr, constraints.minDurationMinutes);
|
|
695
|
+
for (const window of dayWindows) {
|
|
696
|
+
const slotDuration = constraints.preferredDurationMinutes;
|
|
697
|
+
let startMinutes = window.start;
|
|
698
|
+
while (startMinutes + slotDuration <= window.end) {
|
|
699
|
+
if (dayOffset === 0) {
|
|
700
|
+
const currentMinutes = getMinutesOfDay(now, referenceTimeZone);
|
|
701
|
+
if (startMinutes < currentMinutes + 30) {
|
|
702
|
+
startMinutes += 30;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const startDate = dateFromMinutes(dateStr, startMinutes, referenceTimeZone);
|
|
707
|
+
const endDate = dateFromMinutes(dateStr, startMinutes + slotDuration, referenceTimeZone);
|
|
708
|
+
candidateSlots.push({
|
|
709
|
+
start: startDate.toISOString(),
|
|
710
|
+
end: endDate.toISOString(),
|
|
711
|
+
timeZone: referenceTimeZone
|
|
712
|
+
});
|
|
713
|
+
startMinutes += 30;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (candidateSlots.length === 0) {
|
|
718
|
+
const conflicting = this.findConflictingParticipants(availabilities);
|
|
719
|
+
return {
|
|
720
|
+
success: false,
|
|
721
|
+
proposedSlots: [],
|
|
722
|
+
failureReason: "No available time slots found within constraints",
|
|
723
|
+
conflictingParticipants: conflicting
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
const scoredSlots = candidateSlots.map((slot) => this.scoreSlot(slot, request));
|
|
727
|
+
scoredSlots.sort((a, b) => b.score - a.score);
|
|
728
|
+
const proposedSlots = scoredSlots.slice(0, 3);
|
|
729
|
+
return {
|
|
730
|
+
success: true,
|
|
731
|
+
proposedSlots
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
findDayIntersection(availabilities, day, dateStr, minDuration) {
|
|
735
|
+
const participantWindows = availabilities.map(({ availability }) => {
|
|
736
|
+
const exception = availability.exceptions.find((e) => e.date === dateStr);
|
|
737
|
+
if (exception?.unavailable) {
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
if (exception?.startMinutes !== void 0 && exception?.endMinutes !== void 0) {
|
|
741
|
+
return [{ start: exception.startMinutes, end: exception.endMinutes }];
|
|
742
|
+
}
|
|
743
|
+
return availability.weekly.filter((w) => w.day === day).map((w) => ({ start: w.startMinutes, end: w.endMinutes }));
|
|
744
|
+
});
|
|
745
|
+
if (participantWindows.some((windows) => windows.length === 0)) {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
let intersection = participantWindows[0];
|
|
749
|
+
for (let i = 1; i < participantWindows.length; i++) {
|
|
750
|
+
const newIntersection = [];
|
|
751
|
+
for (const windowA of intersection) {
|
|
752
|
+
for (const windowB of participantWindows[i]) {
|
|
753
|
+
const start = Math.max(windowA.start, windowB.start);
|
|
754
|
+
const end = Math.min(windowA.end, windowB.end);
|
|
755
|
+
if (end - start >= minDuration) {
|
|
756
|
+
newIntersection.push({ start, end });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
intersection = newIntersection;
|
|
761
|
+
if (intersection.length === 0) break;
|
|
762
|
+
}
|
|
763
|
+
return intersection;
|
|
764
|
+
}
|
|
765
|
+
findConflictingParticipants(availabilities) {
|
|
766
|
+
const conflicting = [];
|
|
767
|
+
for (let i = 0; i < availabilities.length; i++) {
|
|
768
|
+
let hasOverlapWithAll = true;
|
|
769
|
+
for (let j = 0; j < availabilities.length; j++) {
|
|
770
|
+
if (i === j) continue;
|
|
771
|
+
const hasOverlap = this.hasAnyOverlap(
|
|
772
|
+
availabilities[i].availability,
|
|
773
|
+
availabilities[j].availability
|
|
774
|
+
);
|
|
775
|
+
if (!hasOverlap) {
|
|
776
|
+
hasOverlapWithAll = false;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (!hasOverlapWithAll) {
|
|
781
|
+
conflicting.push(availabilities[i].participant.entityId);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return conflicting;
|
|
785
|
+
}
|
|
786
|
+
hasAnyOverlap(a, b) {
|
|
787
|
+
for (const windowA of a.weekly) {
|
|
788
|
+
for (const windowB of b.weekly) {
|
|
789
|
+
if (windowA.day !== windowB.day) continue;
|
|
790
|
+
const overlapStart = Math.max(windowA.startMinutes, windowB.startMinutes);
|
|
791
|
+
const overlapEnd = Math.min(windowA.endMinutes, windowB.endMinutes);
|
|
792
|
+
if (overlapEnd - overlapStart >= 30) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
scoreSlot(slot, request) {
|
|
800
|
+
const { constraints, urgency } = request;
|
|
801
|
+
let score = 100;
|
|
802
|
+
const reasons = [];
|
|
803
|
+
const concerns = [];
|
|
804
|
+
const startDate = new Date(slot.start);
|
|
805
|
+
const minutes = getMinutesOfDay(startDate, slot.timeZone);
|
|
806
|
+
const day = getDayOfWeek(startDate, slot.timeZone);
|
|
807
|
+
const timeOfDay = minutes < 12 * 60 ? "morning" : minutes < 17 * 60 ? "afternoon" : "evening";
|
|
808
|
+
if (constraints.preferredTimes?.includes(timeOfDay)) {
|
|
809
|
+
score += 20;
|
|
810
|
+
reasons.push(`Preferred time (${timeOfDay})`);
|
|
811
|
+
}
|
|
812
|
+
if (constraints.preferredDays?.includes(day)) {
|
|
813
|
+
score += 15;
|
|
814
|
+
reasons.push(`Preferred day (${day})`);
|
|
815
|
+
}
|
|
816
|
+
const daysFromNow = (startDate.getTime() - Date.now()) / (1e3 * 60 * 60 * 24);
|
|
817
|
+
if (urgency === "urgent") {
|
|
818
|
+
score -= daysFromNow * 10;
|
|
819
|
+
if (daysFromNow < 2) {
|
|
820
|
+
reasons.push("Soon (urgent meeting)");
|
|
821
|
+
}
|
|
822
|
+
} else if (urgency === "soon") {
|
|
823
|
+
score -= daysFromNow * 5;
|
|
824
|
+
}
|
|
825
|
+
if (minutes < 9 * 60) {
|
|
826
|
+
score -= 15;
|
|
827
|
+
concerns.push("Early morning");
|
|
828
|
+
} else if (minutes > 18 * 60) {
|
|
829
|
+
score -= 10;
|
|
830
|
+
concerns.push("Evening time");
|
|
831
|
+
}
|
|
832
|
+
if (minutes >= 10 * 60 && minutes <= 16 * 60) {
|
|
833
|
+
score += 10;
|
|
834
|
+
reasons.push("Standard business hours");
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
slot,
|
|
838
|
+
score: Math.max(0, score),
|
|
839
|
+
reasons,
|
|
840
|
+
concerns
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async createMeeting(request, slot, location) {
|
|
844
|
+
const participants = request.participants.map((p, index) => ({
|
|
845
|
+
entityId: p.entityId,
|
|
846
|
+
name: p.name,
|
|
847
|
+
email: p.email,
|
|
848
|
+
phone: p.phone,
|
|
849
|
+
role: index === 0 ? "organizer" : "required",
|
|
850
|
+
confirmed: false
|
|
851
|
+
}));
|
|
852
|
+
const meeting = {
|
|
853
|
+
id: uuidv42(),
|
|
854
|
+
requestId: request.id,
|
|
855
|
+
roomId: request.roomId,
|
|
856
|
+
title: request.title,
|
|
857
|
+
description: request.description,
|
|
858
|
+
slot,
|
|
859
|
+
location: {
|
|
860
|
+
type: location.type,
|
|
861
|
+
name: location.name,
|
|
862
|
+
address: location.address,
|
|
863
|
+
city: location.city,
|
|
864
|
+
videoUrl: location.videoUrl,
|
|
865
|
+
phoneNumber: location.phoneNumber,
|
|
866
|
+
notes: location.notes
|
|
867
|
+
},
|
|
868
|
+
participants,
|
|
869
|
+
status: "proposed",
|
|
870
|
+
rescheduleCount: 0,
|
|
871
|
+
createdAt: Date.now(),
|
|
872
|
+
updatedAt: Date.now()
|
|
873
|
+
};
|
|
874
|
+
const storage = getMeetingStorage(this.runtime);
|
|
875
|
+
await storage.save(meeting);
|
|
876
|
+
logger.info(`[SchedulingService] Created meeting ${meeting.id} for "${meeting.title}"`);
|
|
877
|
+
if (this.schedulingConfig.autoScheduleReminders) {
|
|
878
|
+
await this.scheduleReminders(meeting);
|
|
879
|
+
}
|
|
880
|
+
return meeting;
|
|
881
|
+
}
|
|
882
|
+
async getMeeting(meetingId) {
|
|
883
|
+
return getMeetingStorage(this.runtime).get(meetingId);
|
|
884
|
+
}
|
|
885
|
+
async getMeetingsForRoom(roomId) {
|
|
886
|
+
return getMeetingStorage(this.runtime).getByRoom(roomId);
|
|
887
|
+
}
|
|
888
|
+
async getUpcomingMeetings(entityId) {
|
|
889
|
+
return getMeetingStorage(this.runtime).getUpcomingForParticipant(entityId);
|
|
890
|
+
}
|
|
891
|
+
async confirmParticipant(meetingId, entityId) {
|
|
892
|
+
const meeting = await this.getMeeting(meetingId);
|
|
893
|
+
if (!meeting) {
|
|
894
|
+
throw new Error(`Meeting not found: ${meetingId}`);
|
|
895
|
+
}
|
|
896
|
+
const participant = meeting.participants.find((p) => p.entityId === entityId);
|
|
897
|
+
if (!participant) {
|
|
898
|
+
throw new Error(`Participant not found in meeting: ${entityId}`);
|
|
899
|
+
}
|
|
900
|
+
participant.confirmed = true;
|
|
901
|
+
participant.confirmedAt = Date.now();
|
|
902
|
+
meeting.updatedAt = Date.now();
|
|
903
|
+
const allConfirmed = meeting.participants.filter((p) => p.role !== "optional").every((p) => p.confirmed);
|
|
904
|
+
if (allConfirmed && meeting.status === "proposed") {
|
|
905
|
+
meeting.status = "confirmed";
|
|
906
|
+
logger.info(`[SchedulingService] Meeting ${meetingId} confirmed by all participants`);
|
|
907
|
+
if (this.schedulingConfig.autoSendCalendarInvites) {
|
|
908
|
+
await this.sendCalendarInvites(meeting);
|
|
909
|
+
meeting.status = "scheduled";
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const storage = getMeetingStorage(this.runtime);
|
|
913
|
+
await storage.save(meeting);
|
|
914
|
+
return meeting;
|
|
915
|
+
}
|
|
916
|
+
async declineParticipant(meetingId, entityId, reason) {
|
|
917
|
+
const meeting = await this.getMeeting(meetingId);
|
|
918
|
+
if (!meeting) {
|
|
919
|
+
throw new Error(`Meeting not found: ${meetingId}`);
|
|
920
|
+
}
|
|
921
|
+
const participant = meeting.participants.find((p) => p.entityId === entityId);
|
|
922
|
+
if (!participant) {
|
|
923
|
+
throw new Error(`Participant not found in meeting: ${entityId}`);
|
|
924
|
+
}
|
|
925
|
+
participant.confirmed = false;
|
|
926
|
+
participant.declineReason = reason;
|
|
927
|
+
meeting.updatedAt = Date.now();
|
|
928
|
+
if (participant.role !== "optional") {
|
|
929
|
+
meeting.status = "rescheduling";
|
|
930
|
+
meeting.cancellationReason = `${participant.name} declined: ${reason || "No reason given"}`;
|
|
931
|
+
logger.info(`[SchedulingService] Meeting ${meetingId} needs rescheduling`);
|
|
932
|
+
}
|
|
933
|
+
const storage = getMeetingStorage(this.runtime);
|
|
934
|
+
await storage.save(meeting);
|
|
935
|
+
return meeting;
|
|
936
|
+
}
|
|
937
|
+
async cancelMeeting(meetingId, reason) {
|
|
938
|
+
const meeting = await this.getMeeting(meetingId);
|
|
939
|
+
if (!meeting) {
|
|
940
|
+
throw new Error(`Meeting not found: ${meetingId}`);
|
|
941
|
+
}
|
|
942
|
+
meeting.status = "cancelled";
|
|
943
|
+
meeting.cancellationReason = reason;
|
|
944
|
+
meeting.updatedAt = Date.now();
|
|
945
|
+
await this.cancelReminders(meetingId);
|
|
946
|
+
const storage = getMeetingStorage(this.runtime);
|
|
947
|
+
await storage.save(meeting);
|
|
948
|
+
logger.info(`[SchedulingService] Meeting ${meetingId} cancelled`);
|
|
949
|
+
return meeting;
|
|
950
|
+
}
|
|
951
|
+
async updateMeetingStatus(meetingId, status) {
|
|
952
|
+
const meeting = await this.getMeeting(meetingId);
|
|
953
|
+
if (!meeting) {
|
|
954
|
+
throw new Error(`Meeting not found: ${meetingId}`);
|
|
955
|
+
}
|
|
956
|
+
meeting.status = status;
|
|
957
|
+
meeting.updatedAt = Date.now();
|
|
958
|
+
const storage = getMeetingStorage(this.runtime);
|
|
959
|
+
await storage.save(meeting);
|
|
960
|
+
return meeting;
|
|
961
|
+
}
|
|
962
|
+
async rescheduleMeeting(meetingId, newSlot, reason) {
|
|
963
|
+
const meeting = await this.getMeeting(meetingId);
|
|
964
|
+
if (!meeting) {
|
|
965
|
+
throw new Error(`Meeting not found: ${meetingId}`);
|
|
966
|
+
}
|
|
967
|
+
await this.cancelReminders(meetingId);
|
|
968
|
+
meeting.slot = newSlot;
|
|
969
|
+
meeting.status = "proposed";
|
|
970
|
+
meeting.rescheduleCount += 1;
|
|
971
|
+
meeting.cancellationReason = reason;
|
|
972
|
+
meeting.updatedAt = Date.now();
|
|
973
|
+
for (const participant of meeting.participants) {
|
|
974
|
+
participant.confirmed = false;
|
|
975
|
+
participant.confirmedAt = void 0;
|
|
976
|
+
}
|
|
977
|
+
const storage = getMeetingStorage(this.runtime);
|
|
978
|
+
await storage.save(meeting);
|
|
979
|
+
if (this.schedulingConfig.autoScheduleReminders) {
|
|
980
|
+
await this.scheduleReminders(meeting);
|
|
981
|
+
}
|
|
982
|
+
logger.info(`[SchedulingService] Meeting ${meetingId} rescheduled (count: ${meeting.rescheduleCount})`);
|
|
983
|
+
return meeting;
|
|
984
|
+
}
|
|
985
|
+
generateCalendarInvite(meeting, recipientEmail, recipientName) {
|
|
986
|
+
const organizer = meeting.participants.find((p) => p.role === "organizer");
|
|
987
|
+
const event = {
|
|
988
|
+
uid: meeting.id,
|
|
989
|
+
title: meeting.title,
|
|
990
|
+
description: meeting.description,
|
|
991
|
+
start: meeting.slot.start,
|
|
992
|
+
end: meeting.slot.end,
|
|
993
|
+
timeZone: meeting.slot.timeZone,
|
|
994
|
+
location: meeting.location.type === "in_person" ? `${meeting.location.name}, ${meeting.location.address}` : meeting.location.videoUrl,
|
|
995
|
+
organizer: organizer?.email ? { name: organizer.name, email: organizer.email } : void 0,
|
|
996
|
+
attendees: meeting.participants.filter((p) => p.email).map((p) => ({
|
|
997
|
+
name: p.name,
|
|
998
|
+
email: p.email,
|
|
999
|
+
role: p.role
|
|
1000
|
+
})),
|
|
1001
|
+
url: meeting.location.videoUrl,
|
|
1002
|
+
reminderMinutes: this.schedulingConfig.defaultReminderMinutes
|
|
1003
|
+
};
|
|
1004
|
+
const ics = generateIcs(event);
|
|
1005
|
+
return {
|
|
1006
|
+
ics,
|
|
1007
|
+
event,
|
|
1008
|
+
recipientEmail,
|
|
1009
|
+
recipientName
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/** Sends invites via EMAIL service if available, otherwise returns ICS for manual handling */
|
|
1013
|
+
async sendCalendarInvites(meeting) {
|
|
1014
|
+
const invites = [];
|
|
1015
|
+
for (const participant of meeting.participants) {
|
|
1016
|
+
if (!participant.email) {
|
|
1017
|
+
logger.warn(
|
|
1018
|
+
`[SchedulingService] Participant ${participant.name} has no email - skipping calendar invite`
|
|
1019
|
+
);
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const invite = this.generateCalendarInvite(meeting, participant.email, participant.name);
|
|
1023
|
+
invites.push(invite);
|
|
1024
|
+
const emailService = this.runtime.getService("EMAIL");
|
|
1025
|
+
const emailServiceAny = emailService;
|
|
1026
|
+
if (emailService && typeof emailServiceAny.sendEmail === "function") {
|
|
1027
|
+
try {
|
|
1028
|
+
await emailServiceAny.sendEmail(
|
|
1029
|
+
participant.email,
|
|
1030
|
+
`Meeting Invite: ${meeting.title}`,
|
|
1031
|
+
this.formatInviteEmail(meeting, participant.name),
|
|
1032
|
+
[{
|
|
1033
|
+
filename: "invite.ics",
|
|
1034
|
+
content: invite.ics,
|
|
1035
|
+
contentType: "text/calendar"
|
|
1036
|
+
}]
|
|
1037
|
+
);
|
|
1038
|
+
logger.info(
|
|
1039
|
+
`[SchedulingService] Sent calendar invite to ${participant.email} for meeting ${meeting.id}`
|
|
1040
|
+
);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
logger.error(
|
|
1043
|
+
`[SchedulingService] Failed to send email to ${participant.email} for meeting ${meeting.id}: ${err}`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
} else {
|
|
1047
|
+
logger.warn(
|
|
1048
|
+
`[SchedulingService] No EMAIL service available - calendar invite for ${participant.email} generated but not sent. ICS content available in returned invite object.`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return invites;
|
|
1053
|
+
}
|
|
1054
|
+
formatInviteEmail(meeting, recipientName) {
|
|
1055
|
+
const startDate = new Date(meeting.slot.start);
|
|
1056
|
+
const endDate = new Date(meeting.slot.end);
|
|
1057
|
+
const dateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
1058
|
+
timeZone: meeting.slot.timeZone,
|
|
1059
|
+
weekday: "long",
|
|
1060
|
+
year: "numeric",
|
|
1061
|
+
month: "long",
|
|
1062
|
+
day: "numeric"
|
|
1063
|
+
});
|
|
1064
|
+
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
1065
|
+
timeZone: meeting.slot.timeZone,
|
|
1066
|
+
hour: "numeric",
|
|
1067
|
+
minute: "2-digit",
|
|
1068
|
+
timeZoneName: "short"
|
|
1069
|
+
});
|
|
1070
|
+
let locationText = "";
|
|
1071
|
+
if (meeting.location.type === "virtual" && meeting.location.videoUrl) {
|
|
1072
|
+
locationText = `Join online: ${meeting.location.videoUrl}`;
|
|
1073
|
+
} else if (meeting.location.type === "in_person") {
|
|
1074
|
+
locationText = `Location: ${meeting.location.name}, ${meeting.location.address}`;
|
|
1075
|
+
} else if (meeting.location.type === "phone" && meeting.location.phoneNumber) {
|
|
1076
|
+
locationText = `Call: ${meeting.location.phoneNumber}`;
|
|
1077
|
+
}
|
|
1078
|
+
return `Hi ${recipientName},
|
|
1079
|
+
|
|
1080
|
+
You're invited to: ${meeting.title}
|
|
1081
|
+
|
|
1082
|
+
When: ${dateFormatter.format(startDate)}
|
|
1083
|
+
Time: ${timeFormatter.format(startDate)} - ${timeFormatter.format(endDate)}
|
|
1084
|
+
${locationText}
|
|
1085
|
+
|
|
1086
|
+
${meeting.description || ""}
|
|
1087
|
+
|
|
1088
|
+
Please add the attached calendar invite to your calendar.
|
|
1089
|
+
|
|
1090
|
+
See you there!`;
|
|
1091
|
+
}
|
|
1092
|
+
async scheduleReminders(meeting) {
|
|
1093
|
+
const reminders = [];
|
|
1094
|
+
const meetingTime = new Date(meeting.slot.start).getTime();
|
|
1095
|
+
for (const minutesBefore of this.schedulingConfig.defaultReminderMinutes) {
|
|
1096
|
+
const scheduledFor = new Date(meetingTime - minutesBefore * 60 * 1e3);
|
|
1097
|
+
if (scheduledFor.getTime() < Date.now()) continue;
|
|
1098
|
+
for (const participant of meeting.participants) {
|
|
1099
|
+
const type = participant.phone ? "sms" : participant.email ? "email" : "push";
|
|
1100
|
+
const timeLabel = minutesBefore >= 1440 ? `${Math.round(minutesBefore / 1440)} day(s)` : minutesBefore >= 60 ? `${Math.round(minutesBefore / 60)} hour(s)` : `${minutesBefore} minutes`;
|
|
1101
|
+
const reminder = {
|
|
1102
|
+
id: uuidv42(),
|
|
1103
|
+
meetingId: meeting.id,
|
|
1104
|
+
participantId: participant.entityId,
|
|
1105
|
+
scheduledFor: scheduledFor.toISOString(),
|
|
1106
|
+
type,
|
|
1107
|
+
message: `Reminder: "${meeting.title}" is in ${timeLabel}`,
|
|
1108
|
+
status: "pending",
|
|
1109
|
+
createdAt: Date.now()
|
|
1110
|
+
};
|
|
1111
|
+
reminders.push(reminder);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const storage = getReminderStorage(this.runtime);
|
|
1115
|
+
for (const reminder of reminders) {
|
|
1116
|
+
await storage.save(reminder);
|
|
1117
|
+
}
|
|
1118
|
+
logger.debug(`[SchedulingService] Scheduled ${reminders.length} reminders for meeting ${meeting.id}`);
|
|
1119
|
+
return reminders;
|
|
1120
|
+
}
|
|
1121
|
+
async getDueReminders() {
|
|
1122
|
+
return getReminderStorage(this.runtime).getDue();
|
|
1123
|
+
}
|
|
1124
|
+
async markReminderSent(reminderId) {
|
|
1125
|
+
const storage = getReminderStorage(this.runtime);
|
|
1126
|
+
const reminder = await storage.get(reminderId);
|
|
1127
|
+
if (!reminder) return;
|
|
1128
|
+
reminder.status = "sent";
|
|
1129
|
+
reminder.sentAt = Date.now();
|
|
1130
|
+
await storage.save(reminder);
|
|
1131
|
+
}
|
|
1132
|
+
async cancelReminders(meetingId) {
|
|
1133
|
+
const storage = getReminderStorage(this.runtime);
|
|
1134
|
+
const reminders = await storage.getByMeeting(meetingId);
|
|
1135
|
+
for (const reminder of reminders) {
|
|
1136
|
+
if (reminder.status === "pending") {
|
|
1137
|
+
reminder.status = "cancelled";
|
|
1138
|
+
await storage.save(reminder);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
formatSlot(slot, locale = "en-US") {
|
|
1143
|
+
const start = new Date(slot.start);
|
|
1144
|
+
const end = new Date(slot.end);
|
|
1145
|
+
const dateFormatter = new Intl.DateTimeFormat(locale, {
|
|
1146
|
+
timeZone: slot.timeZone,
|
|
1147
|
+
weekday: "short",
|
|
1148
|
+
month: "short",
|
|
1149
|
+
day: "numeric"
|
|
1150
|
+
});
|
|
1151
|
+
const timeFormatter = new Intl.DateTimeFormat(locale, {
|
|
1152
|
+
timeZone: slot.timeZone,
|
|
1153
|
+
hour: "numeric",
|
|
1154
|
+
minute: "2-digit"
|
|
1155
|
+
});
|
|
1156
|
+
return `${dateFormatter.format(start)}, ${timeFormatter.format(start)} - ${timeFormatter.format(end)}`;
|
|
1157
|
+
}
|
|
1158
|
+
getConfig() {
|
|
1159
|
+
return { ...this.schedulingConfig };
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// src/actions/schedule-meeting.ts
|
|
1164
|
+
var parseMeetingRequest = (text) => {
|
|
1165
|
+
const normalized = text.toLowerCase();
|
|
1166
|
+
const result = {};
|
|
1167
|
+
const titleMatch = /(?:schedule|book|arrange|set up|plan)\s+(?:a\s+)?(?:meeting|call|chat)\s+(?:about|for|regarding|to discuss)\s+["']?([^"'\n.]+)["']?/i.exec(
|
|
1168
|
+
text
|
|
1169
|
+
);
|
|
1170
|
+
if (titleMatch) {
|
|
1171
|
+
result.title = titleMatch[1].trim();
|
|
1172
|
+
}
|
|
1173
|
+
const durationMatch = /(\d+)\s*(?:minute|min|hour|hr)/i.exec(normalized);
|
|
1174
|
+
if (durationMatch) {
|
|
1175
|
+
let duration = Number.parseInt(durationMatch[1], 10);
|
|
1176
|
+
if (normalized.includes("hour") || normalized.includes("hr")) {
|
|
1177
|
+
duration *= 60;
|
|
1178
|
+
}
|
|
1179
|
+
result.duration = duration;
|
|
1180
|
+
}
|
|
1181
|
+
if (normalized.includes("urgent") || normalized.includes("asap") || normalized.includes("immediately")) {
|
|
1182
|
+
result.urgency = "urgent";
|
|
1183
|
+
} else if (normalized.includes("soon") || normalized.includes("this week")) {
|
|
1184
|
+
result.urgency = "soon";
|
|
1185
|
+
} else {
|
|
1186
|
+
result.urgency = "flexible";
|
|
1187
|
+
}
|
|
1188
|
+
return result;
|
|
1189
|
+
};
|
|
1190
|
+
var formatProposedSlots = (slots) => {
|
|
1191
|
+
if (slots.length === 0) {
|
|
1192
|
+
return "I couldn't find any available time slots.";
|
|
1193
|
+
}
|
|
1194
|
+
const options = {
|
|
1195
|
+
weekday: "short",
|
|
1196
|
+
month: "short",
|
|
1197
|
+
day: "numeric",
|
|
1198
|
+
hour: "numeric",
|
|
1199
|
+
minute: "2-digit"
|
|
1200
|
+
};
|
|
1201
|
+
const formatted = slots.map((proposal, index) => {
|
|
1202
|
+
const start = new Date(proposal.slot.start);
|
|
1203
|
+
const end = new Date(proposal.slot.end);
|
|
1204
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
1205
|
+
...options,
|
|
1206
|
+
timeZone: proposal.slot.timeZone
|
|
1207
|
+
});
|
|
1208
|
+
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
1209
|
+
hour: "numeric",
|
|
1210
|
+
minute: "2-digit",
|
|
1211
|
+
timeZone: proposal.slot.timeZone
|
|
1212
|
+
});
|
|
1213
|
+
const dateStr = formatter.format(start);
|
|
1214
|
+
const endTimeStr = timeFormatter.format(end);
|
|
1215
|
+
let entry = `${index + 1}. ${dateStr} - ${endTimeStr}`;
|
|
1216
|
+
if (proposal.reasons.length > 0) {
|
|
1217
|
+
entry += ` (${proposal.reasons[0]})`;
|
|
1218
|
+
}
|
|
1219
|
+
return entry;
|
|
1220
|
+
});
|
|
1221
|
+
return `Here are some times that work:
|
|
1222
|
+
|
|
1223
|
+
${formatted.join("\n")}
|
|
1224
|
+
|
|
1225
|
+
Which option works best for you? Just say the number.`;
|
|
1226
|
+
};
|
|
1227
|
+
var scheduleMeetingAction = {
|
|
1228
|
+
name: "SCHEDULE_MEETING",
|
|
1229
|
+
similes: [
|
|
1230
|
+
"BOOK_MEETING",
|
|
1231
|
+
"ARRANGE_MEETING",
|
|
1232
|
+
"SET_UP_MEETING",
|
|
1233
|
+
"PLAN_MEETING",
|
|
1234
|
+
"CREATE_MEETING"
|
|
1235
|
+
],
|
|
1236
|
+
description: "Schedule a meeting between multiple participants by finding a suitable time slot",
|
|
1237
|
+
validate: async (_runtime, message) => {
|
|
1238
|
+
const text = message.content?.text?.toLowerCase() ?? "";
|
|
1239
|
+
return text.includes("schedule") || text.includes("book") || text.includes("arrange") || text.includes("set up") || text.includes("plan") || text.includes("meet") && !text.includes("nice to meet");
|
|
1240
|
+
},
|
|
1241
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
1242
|
+
const schedulingService = runtime.getService("SCHEDULING");
|
|
1243
|
+
if (!schedulingService) {
|
|
1244
|
+
await callback?.({
|
|
1245
|
+
text: "Scheduling service is not available. Please try again later."
|
|
1246
|
+
});
|
|
1247
|
+
return { success: false };
|
|
1248
|
+
}
|
|
1249
|
+
const entityId = message.entityId;
|
|
1250
|
+
const roomId = message.roomId;
|
|
1251
|
+
if (!entityId || !roomId) {
|
|
1252
|
+
await callback?.({
|
|
1253
|
+
text: "I could not identify the conversation context. Please try again."
|
|
1254
|
+
});
|
|
1255
|
+
return { success: false };
|
|
1256
|
+
}
|
|
1257
|
+
const text = message.content?.text ?? "";
|
|
1258
|
+
const parsed = parseMeetingRequest(text);
|
|
1259
|
+
const userAvailability = await schedulingService.getAvailability(entityId);
|
|
1260
|
+
if (!userAvailability || userAvailability.weekly.length === 0) {
|
|
1261
|
+
await callback?.({
|
|
1262
|
+
text: `I don't have your availability yet. Tell me when you're free, e.g. "weekdays 9am-5pm"`
|
|
1263
|
+
});
|
|
1264
|
+
return { success: false };
|
|
1265
|
+
}
|
|
1266
|
+
const participants = [
|
|
1267
|
+
{ entityId, name: "You", availability: userAvailability }
|
|
1268
|
+
];
|
|
1269
|
+
const title = parsed.title || "Meeting";
|
|
1270
|
+
const request = await schedulingService.createSchedulingRequest(
|
|
1271
|
+
roomId,
|
|
1272
|
+
title,
|
|
1273
|
+
participants,
|
|
1274
|
+
{
|
|
1275
|
+
preferredDurationMinutes: parsed.duration || 30,
|
|
1276
|
+
maxDaysOut: parsed.urgency === "urgent" ? 3 : parsed.urgency === "soon" ? 7 : 14
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
urgency: parsed.urgency
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
const result = await schedulingService.findAvailableSlots(request);
|
|
1283
|
+
if (!result.success || result.proposedSlots.length === 0) {
|
|
1284
|
+
await callback?.({ text: result.failureReason || "No available slots found. Try expanding your availability?" });
|
|
1285
|
+
return { success: false };
|
|
1286
|
+
}
|
|
1287
|
+
await callback?.({ text: formatProposedSlots(result.proposedSlots) });
|
|
1288
|
+
return {
|
|
1289
|
+
success: true,
|
|
1290
|
+
data: {
|
|
1291
|
+
requestId: request.id,
|
|
1292
|
+
proposedSlots: result.proposedSlots
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
},
|
|
1296
|
+
examples: [
|
|
1297
|
+
[
|
|
1298
|
+
{
|
|
1299
|
+
name: "{{user1}}",
|
|
1300
|
+
content: { text: "Can you schedule a meeting for me?" }
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "{{agentName}}",
|
|
1304
|
+
content: {
|
|
1305
|
+
text: "Here are some times that work:\n\n1. Mon, Jan 20 at 10:00am - 10:30am (Standard business hours)\n2. Mon, Jan 20 at 2:00pm - 2:30pm (Standard business hours)\n3. Tue, Jan 21 at 9:00am - 9:30am (Preferred time)\n\nWhich option works best for you? Just say the number."
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
],
|
|
1309
|
+
[
|
|
1310
|
+
{
|
|
1311
|
+
name: "{{user1}}",
|
|
1312
|
+
content: { text: "I'd like to set up a call for next week" }
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: "{{agentName}}",
|
|
1316
|
+
content: {
|
|
1317
|
+
text: "Here are some times that work:\n\n1. Mon, Jan 20 at 10:00am - 10:30am (Standard business hours)\n2. Tue, Jan 21 at 2:00pm - 2:30pm (Preferred day)\n3. Wed, Jan 22 at 11:00am - 11:30am (Standard business hours)\n\nWhich option works best for you? Just say the number."
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
]
|
|
1321
|
+
]
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
// src/actions/confirm-meeting.ts
|
|
1325
|
+
var confirmMeetingAction = {
|
|
1326
|
+
name: "CONFIRM_MEETING",
|
|
1327
|
+
similes: [
|
|
1328
|
+
"ACCEPT_MEETING",
|
|
1329
|
+
"CONFIRM_ATTENDANCE",
|
|
1330
|
+
"RSVP_YES",
|
|
1331
|
+
"DECLINE_MEETING",
|
|
1332
|
+
"CANCEL_ATTENDANCE"
|
|
1333
|
+
],
|
|
1334
|
+
description: "Confirm or decline attendance for a scheduled meeting",
|
|
1335
|
+
validate: async (_runtime, message) => {
|
|
1336
|
+
const text = message.content?.text?.toLowerCase() ?? "";
|
|
1337
|
+
return text.includes("confirm") && text.includes("meeting") || text.includes("accept") && text.includes("meeting") || text.includes("decline") && text.includes("meeting") || text.includes("rsvp") || text.includes("i'll be there") || text.includes("i can't make it");
|
|
1338
|
+
},
|
|
1339
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
1340
|
+
const schedulingService = runtime.getService("SCHEDULING");
|
|
1341
|
+
if (!schedulingService) {
|
|
1342
|
+
await callback?.({
|
|
1343
|
+
text: "Scheduling service is not available. Please try again later."
|
|
1344
|
+
});
|
|
1345
|
+
return { success: false };
|
|
1346
|
+
}
|
|
1347
|
+
const text = message.content?.text?.toLowerCase() ?? "";
|
|
1348
|
+
const isConfirming = text.includes("confirm") || text.includes("accept") || text.includes("i'll be there") || text.includes("yes");
|
|
1349
|
+
const meetings = await schedulingService.getUpcomingMeetings(message.entityId);
|
|
1350
|
+
if (meetings.length === 0) {
|
|
1351
|
+
await callback?.({
|
|
1352
|
+
text: "You don't have any upcoming meetings to confirm."
|
|
1353
|
+
});
|
|
1354
|
+
return { success: false };
|
|
1355
|
+
}
|
|
1356
|
+
const pendingMeetings = meetings.filter((m) => m.status === "proposed");
|
|
1357
|
+
if (pendingMeetings.length === 0) {
|
|
1358
|
+
await callback?.({
|
|
1359
|
+
text: "All your upcoming meetings have already been confirmed."
|
|
1360
|
+
});
|
|
1361
|
+
return { success: true };
|
|
1362
|
+
}
|
|
1363
|
+
const meeting = pendingMeetings[0];
|
|
1364
|
+
if (isConfirming) {
|
|
1365
|
+
await schedulingService.confirmParticipant(meeting.id, message.entityId);
|
|
1366
|
+
const formattedTime = schedulingService.formatSlot(meeting.slot);
|
|
1367
|
+
await callback?.({
|
|
1368
|
+
text: `Great! I've confirmed your attendance for "${meeting.title}" on ${formattedTime}. You'll receive a calendar invite shortly.`
|
|
1369
|
+
});
|
|
1370
|
+
} else {
|
|
1371
|
+
await schedulingService.declineParticipant(
|
|
1372
|
+
meeting.id,
|
|
1373
|
+
message.entityId,
|
|
1374
|
+
"User declined via chat"
|
|
1375
|
+
);
|
|
1376
|
+
await callback?.({
|
|
1377
|
+
text: `I've noted that you can't make it to "${meeting.title}". I'll let the other participants know and see if we can find another time.`
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
return { success: true };
|
|
1381
|
+
},
|
|
1382
|
+
examples: [
|
|
1383
|
+
[
|
|
1384
|
+
{
|
|
1385
|
+
name: "{{user1}}",
|
|
1386
|
+
content: { text: "Yes, I'll be there for the meeting" }
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
name: "{{agentName}}",
|
|
1390
|
+
content: {
|
|
1391
|
+
text: `Great! I've confirmed your attendance for "Coffee Chat" on Mon, Jan 20, 10:00 AM - 11:00 AM. You'll receive a calendar invite shortly.`
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
[
|
|
1396
|
+
{
|
|
1397
|
+
name: "{{user1}}",
|
|
1398
|
+
content: { text: "I can't make the meeting, something came up" }
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
name: "{{agentName}}",
|
|
1402
|
+
content: {
|
|
1403
|
+
text: `I've noted that you can't make it to "Coffee Chat". I'll let the other participants know and see if we can find another time.`
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
]
|
|
1407
|
+
]
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
// src/actions/set-availability.ts
|
|
1411
|
+
var DAY_NAMES = {
|
|
1412
|
+
monday: "mon",
|
|
1413
|
+
mon: "mon",
|
|
1414
|
+
tuesday: "tue",
|
|
1415
|
+
tue: "tue",
|
|
1416
|
+
wednesday: "wed",
|
|
1417
|
+
wed: "wed",
|
|
1418
|
+
thursday: "thu",
|
|
1419
|
+
thu: "thu",
|
|
1420
|
+
friday: "fri",
|
|
1421
|
+
fri: "fri",
|
|
1422
|
+
saturday: "sat",
|
|
1423
|
+
sat: "sat",
|
|
1424
|
+
sunday: "sun",
|
|
1425
|
+
sun: "sun"
|
|
1426
|
+
};
|
|
1427
|
+
var TIME_PRESETS = {
|
|
1428
|
+
morning: { start: 540, end: 720 },
|
|
1429
|
+
afternoon: { start: 720, end: 1020 },
|
|
1430
|
+
evening: { start: 1020, end: 1260 },
|
|
1431
|
+
"business hours": { start: 540, end: 1020 },
|
|
1432
|
+
"work hours": { start: 540, end: 1020 }
|
|
1433
|
+
};
|
|
1434
|
+
var parseTimeToMinutes = (timeStr) => {
|
|
1435
|
+
const normalized = timeStr.toLowerCase().trim();
|
|
1436
|
+
let match = /^(\d{1,2}):(\d{2})$/.exec(normalized);
|
|
1437
|
+
if (match) {
|
|
1438
|
+
const hours = Number.parseInt(match[1], 10);
|
|
1439
|
+
const minutes = Number.parseInt(match[2], 10);
|
|
1440
|
+
if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60) {
|
|
1441
|
+
return hours * 60 + minutes;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
match = /^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/i.exec(normalized);
|
|
1445
|
+
if (match) {
|
|
1446
|
+
let hours = Number.parseInt(match[1], 10);
|
|
1447
|
+
const minutes = match[2] ? Number.parseInt(match[2], 10) : 0;
|
|
1448
|
+
const isPm = match[3].toLowerCase() === "pm";
|
|
1449
|
+
if (hours === 12) {
|
|
1450
|
+
hours = isPm ? 12 : 0;
|
|
1451
|
+
} else if (isPm) {
|
|
1452
|
+
hours += 12;
|
|
1453
|
+
}
|
|
1454
|
+
if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60) {
|
|
1455
|
+
return hours * 60 + minutes;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return null;
|
|
1459
|
+
};
|
|
1460
|
+
var parseDays = (dayStr) => {
|
|
1461
|
+
const normalized = dayStr.toLowerCase().trim();
|
|
1462
|
+
if (normalized === "weekday" || normalized === "weekdays") {
|
|
1463
|
+
return ["mon", "tue", "wed", "thu", "fri"];
|
|
1464
|
+
}
|
|
1465
|
+
if (normalized === "weekend" || normalized === "weekends") {
|
|
1466
|
+
return ["sat", "sun"];
|
|
1467
|
+
}
|
|
1468
|
+
if (normalized === "everyday" || normalized === "every day" || normalized === "daily") {
|
|
1469
|
+
return ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
1470
|
+
}
|
|
1471
|
+
const day = DAY_NAMES[normalized];
|
|
1472
|
+
return day ? [day] : [];
|
|
1473
|
+
};
|
|
1474
|
+
var parseAvailabilityText = (text) => {
|
|
1475
|
+
const normalized = text.toLowerCase();
|
|
1476
|
+
const windows = [];
|
|
1477
|
+
let timeZone;
|
|
1478
|
+
const tzMatch = /(?:time\s*zone|tz|timezone)[\s:]*([A-Za-z_\/]+)/i.exec(text) || /(America\/[A-Za-z_]+|Europe\/[A-Za-z_]+|Asia\/[A-Za-z_]+|Pacific\/[A-Za-z_]+|UTC)/i.exec(text);
|
|
1479
|
+
if (tzMatch) {
|
|
1480
|
+
timeZone = tzMatch[1];
|
|
1481
|
+
}
|
|
1482
|
+
const dayTimePattern = /(weekdays?|weekends?|monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun|daily|every\s*day)(?:\s+(?:and\s+)?(?:weekdays?|weekends?|monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun))*\s+(?:from\s+)?(\d{1,2}(?::\d{2})?\s*(?:am|pm)?)\s*(?:to|-)\s*(\d{1,2}(?::\d{2})?\s*(?:am|pm)?)/gi;
|
|
1483
|
+
let match;
|
|
1484
|
+
while ((match = dayTimePattern.exec(normalized)) !== null) {
|
|
1485
|
+
const dayPart = match[1];
|
|
1486
|
+
const startTime = match[2];
|
|
1487
|
+
const endTime = match[3];
|
|
1488
|
+
const days = parseDays(dayPart);
|
|
1489
|
+
const startMinutes = parseTimeToMinutes(startTime);
|
|
1490
|
+
const endMinutes = parseTimeToMinutes(endTime);
|
|
1491
|
+
if (days.length > 0 && startMinutes !== null && endMinutes !== null) {
|
|
1492
|
+
for (const day of days) {
|
|
1493
|
+
windows.push({
|
|
1494
|
+
day,
|
|
1495
|
+
startMinutes,
|
|
1496
|
+
endMinutes
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const dayPresetPattern = /(weekdays?|weekends?|monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun|daily|every\s*day)\s+(morning|afternoon|evening|business\s*hours?|work\s*hours?)/gi;
|
|
1502
|
+
while ((match = dayPresetPattern.exec(normalized)) !== null) {
|
|
1503
|
+
const dayPart = match[1];
|
|
1504
|
+
const timePart = match[2].toLowerCase();
|
|
1505
|
+
const days = parseDays(dayPart);
|
|
1506
|
+
const timeRange = TIME_PRESETS[timePart] || TIME_PRESETS[timePart.replace(/s$/, "")];
|
|
1507
|
+
if (days.length > 0 && timeRange) {
|
|
1508
|
+
for (const day of days) {
|
|
1509
|
+
const exists = windows.some(
|
|
1510
|
+
(w) => w.day === day && w.startMinutes === timeRange.start && w.endMinutes === timeRange.end
|
|
1511
|
+
);
|
|
1512
|
+
if (!exists) {
|
|
1513
|
+
windows.push({
|
|
1514
|
+
day,
|
|
1515
|
+
startMinutes: timeRange.start,
|
|
1516
|
+
endMinutes: timeRange.end
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (windows.length === 0) {
|
|
1523
|
+
for (const [preset, range] of Object.entries(TIME_PRESETS)) {
|
|
1524
|
+
if (normalized.includes(preset)) {
|
|
1525
|
+
for (const day of ["mon", "tue", "wed", "thu", "fri"]) {
|
|
1526
|
+
windows.push({
|
|
1527
|
+
day,
|
|
1528
|
+
startMinutes: range.start,
|
|
1529
|
+
endMinutes: range.end
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (windows.length === 0) {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
return { windows, timeZone };
|
|
1540
|
+
};
|
|
1541
|
+
var formatTime = (minutes) => {
|
|
1542
|
+
const hours = Math.floor(minutes / 60);
|
|
1543
|
+
const mins = minutes % 60;
|
|
1544
|
+
const period = hours >= 12 ? "pm" : "am";
|
|
1545
|
+
const displayHours = hours > 12 ? hours - 12 : hours === 0 ? 12 : hours;
|
|
1546
|
+
return mins > 0 ? `${displayHours}:${mins.toString().padStart(2, "0")}${period}` : `${displayHours}${period}`;
|
|
1547
|
+
};
|
|
1548
|
+
var formatDay = (day) => {
|
|
1549
|
+
const names = {
|
|
1550
|
+
mon: "Monday",
|
|
1551
|
+
tue: "Tuesday",
|
|
1552
|
+
wed: "Wednesday",
|
|
1553
|
+
thu: "Thursday",
|
|
1554
|
+
fri: "Friday",
|
|
1555
|
+
sat: "Saturday",
|
|
1556
|
+
sun: "Sunday"
|
|
1557
|
+
};
|
|
1558
|
+
return names[day];
|
|
1559
|
+
};
|
|
1560
|
+
var setAvailabilityAction = {
|
|
1561
|
+
name: "SET_AVAILABILITY",
|
|
1562
|
+
similes: [
|
|
1563
|
+
"UPDATE_AVAILABILITY",
|
|
1564
|
+
"SET_SCHEDULE",
|
|
1565
|
+
"UPDATE_SCHEDULE",
|
|
1566
|
+
"SET_FREE_TIME",
|
|
1567
|
+
"WHEN_FREE"
|
|
1568
|
+
],
|
|
1569
|
+
description: "Set the user's availability for scheduling meetings",
|
|
1570
|
+
validate: async (_runtime, message) => {
|
|
1571
|
+
const text = message.content?.text?.toLowerCase() ?? "";
|
|
1572
|
+
return text.includes("available") || text.includes("availability") || text.includes("free on") || text.includes("i'm free") || text.includes("can meet") || text.includes("my time") || text.includes("morning") || text.includes("afternoon") || text.includes("evening");
|
|
1573
|
+
},
|
|
1574
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
1575
|
+
const schedulingService = runtime.getService("SCHEDULING");
|
|
1576
|
+
if (!schedulingService) {
|
|
1577
|
+
await callback?.({
|
|
1578
|
+
text: "Scheduling service is not available. Please try again later."
|
|
1579
|
+
});
|
|
1580
|
+
return { success: false };
|
|
1581
|
+
}
|
|
1582
|
+
const entityId = message.entityId;
|
|
1583
|
+
if (!entityId) {
|
|
1584
|
+
await callback?.({
|
|
1585
|
+
text: "I could not identify you. Please try again."
|
|
1586
|
+
});
|
|
1587
|
+
return { success: false };
|
|
1588
|
+
}
|
|
1589
|
+
const text = message.content?.text ?? "";
|
|
1590
|
+
const parsed = parseAvailabilityText(text);
|
|
1591
|
+
if (!parsed || parsed.windows.length === 0) {
|
|
1592
|
+
await callback?.({
|
|
1593
|
+
text: `I couldn't understand your availability. Try: "weekdays 9am-5pm" or "Monday afternoons"`
|
|
1594
|
+
});
|
|
1595
|
+
return { success: false };
|
|
1596
|
+
}
|
|
1597
|
+
const defaultTimeZone = process.env.DEFAULT_TIMEZONE ?? "America/New_York";
|
|
1598
|
+
let availability = await schedulingService.getAvailability(entityId);
|
|
1599
|
+
if (!availability) {
|
|
1600
|
+
availability = { timeZone: parsed.timeZone || defaultTimeZone, weekly: [], exceptions: [] };
|
|
1601
|
+
}
|
|
1602
|
+
if (parsed.timeZone) availability.timeZone = parsed.timeZone;
|
|
1603
|
+
for (const newWindow of parsed.windows) {
|
|
1604
|
+
const exists = availability.weekly.some(
|
|
1605
|
+
(w) => w.day === newWindow.day && w.startMinutes === newWindow.startMinutes && w.endMinutes === newWindow.endMinutes
|
|
1606
|
+
);
|
|
1607
|
+
if (!exists) availability.weekly.push(newWindow);
|
|
1608
|
+
}
|
|
1609
|
+
await schedulingService.saveAvailability(entityId, availability);
|
|
1610
|
+
const addedWindows = parsed.windows.map((w) => `${formatDay(w.day)} ${formatTime(w.startMinutes)}-${formatTime(w.endMinutes)}`).join(", ");
|
|
1611
|
+
await callback?.({
|
|
1612
|
+
text: `Got it! I've saved your availability: ${addedWindows}. I'll use this to find meeting times that work for you.`
|
|
1613
|
+
});
|
|
1614
|
+
return { success: true };
|
|
1615
|
+
},
|
|
1616
|
+
examples: [
|
|
1617
|
+
[
|
|
1618
|
+
{
|
|
1619
|
+
name: "{{user1}}",
|
|
1620
|
+
content: { text: "I'm free weekdays 9am to 5pm" }
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
name: "{{agentName}}",
|
|
1624
|
+
content: {
|
|
1625
|
+
text: "Got it! I've saved your availability: Monday 9am-5pm, Tuesday 9am-5pm, Wednesday 9am-5pm, Thursday 9am-5pm, Friday 9am-5pm. I'll use this to find meeting times that work for you."
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
],
|
|
1629
|
+
[
|
|
1630
|
+
{
|
|
1631
|
+
name: "{{user1}}",
|
|
1632
|
+
content: { text: "Available Monday afternoons and Wednesday mornings" }
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
name: "{{agentName}}",
|
|
1636
|
+
content: {
|
|
1637
|
+
text: "Got it! I've saved your availability: Monday 12pm-5pm, Wednesday 9am-12pm. I'll use this to find meeting times that work for you."
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
]
|
|
1641
|
+
]
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
// src/providers/scheduling-context.ts
|
|
1645
|
+
var formatMeetingForContext = (meeting, service) => {
|
|
1646
|
+
const timeStr = service.formatSlot(meeting.slot);
|
|
1647
|
+
const participants = meeting.participants.map((p) => p.name).join(", ");
|
|
1648
|
+
const locationStr = meeting.location.type === "in_person" ? `at ${meeting.location.name}` : meeting.location.type === "virtual" ? "virtual meeting" : "phone call";
|
|
1649
|
+
return `- "${meeting.title}" on ${timeStr} (${locationStr}) with ${participants} [${meeting.status}]`;
|
|
1650
|
+
};
|
|
1651
|
+
var schedulingContextProvider = {
|
|
1652
|
+
name: "SCHEDULING_CONTEXT",
|
|
1653
|
+
description: "Provides context about upcoming meetings and scheduling requests",
|
|
1654
|
+
get: async (runtime, message, _state) => {
|
|
1655
|
+
const schedulingService = runtime.getService("SCHEDULING");
|
|
1656
|
+
if (!schedulingService) {
|
|
1657
|
+
return { text: "" };
|
|
1658
|
+
}
|
|
1659
|
+
const entityId = message.entityId;
|
|
1660
|
+
if (!entityId) {
|
|
1661
|
+
return { text: "" };
|
|
1662
|
+
}
|
|
1663
|
+
const sections = [];
|
|
1664
|
+
try {
|
|
1665
|
+
const meetings = await schedulingService.getUpcomingMeetings(entityId);
|
|
1666
|
+
if (meetings.length > 0) {
|
|
1667
|
+
const proposedMeetings = meetings.filter((m) => m.status === "proposed");
|
|
1668
|
+
const confirmedMeetings = meetings.filter(
|
|
1669
|
+
(m) => m.status === "confirmed" || m.status === "scheduled"
|
|
1670
|
+
);
|
|
1671
|
+
if (proposedMeetings.length > 0) {
|
|
1672
|
+
sections.push("Meetings pending confirmation:");
|
|
1673
|
+
for (const meeting of proposedMeetings.slice(0, 3)) {
|
|
1674
|
+
sections.push(formatMeetingForContext(meeting, schedulingService));
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (confirmedMeetings.length > 0) {
|
|
1678
|
+
sections.push("\nUpcoming confirmed meetings:");
|
|
1679
|
+
for (const meeting of confirmedMeetings.slice(0, 5)) {
|
|
1680
|
+
sections.push(formatMeetingForContext(meeting, schedulingService));
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const availability = await schedulingService.getAvailability(entityId);
|
|
1685
|
+
if (availability) {
|
|
1686
|
+
const weeklyCount = availability.weekly.length;
|
|
1687
|
+
const exceptionsCount = availability.exceptions.length;
|
|
1688
|
+
sections.push(
|
|
1689
|
+
`
|
|
1690
|
+
User has ${weeklyCount} recurring availability windows set (timezone: ${availability.timeZone})`
|
|
1691
|
+
);
|
|
1692
|
+
if (exceptionsCount > 0) {
|
|
1693
|
+
sections.push(`User has ${exceptionsCount} availability exceptions`);
|
|
1694
|
+
}
|
|
1695
|
+
} else {
|
|
1696
|
+
sections.push("\nUser has not set their availability yet");
|
|
1697
|
+
}
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
if (sections.length === 0) {
|
|
1701
|
+
return { text: "" };
|
|
1702
|
+
}
|
|
1703
|
+
return { text: `<scheduling_context>
|
|
1704
|
+
${sections.join("\n")}
|
|
1705
|
+
</scheduling_context>` };
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
// src/index.ts
|
|
1710
|
+
var schedulingPlugin = {
|
|
1711
|
+
name: "scheduling",
|
|
1712
|
+
description: "Scheduling and calendar coordination for multi-party meetings",
|
|
1713
|
+
// Register the scheduling service
|
|
1714
|
+
services: [SchedulingService],
|
|
1715
|
+
// Actions for scheduling operations
|
|
1716
|
+
actions: [scheduleMeetingAction, confirmMeetingAction, setAvailabilityAction],
|
|
1717
|
+
// Provider for scheduling context
|
|
1718
|
+
providers: [schedulingContextProvider]
|
|
1719
|
+
};
|
|
1720
|
+
var index_default = schedulingPlugin;
|
|
1721
|
+
export {
|
|
1722
|
+
DEFAULT_CONFIG,
|
|
1723
|
+
SchedulingService,
|
|
1724
|
+
index_default as default,
|
|
1725
|
+
generateIcs,
|
|
1726
|
+
getAvailabilityStorage,
|
|
1727
|
+
getMeetingStorage,
|
|
1728
|
+
getReminderStorage,
|
|
1729
|
+
getSchedulingRequestStorage,
|
|
1730
|
+
parseIcs,
|
|
1731
|
+
schedulingPlugin
|
|
1732
|
+
};
|