@geminixiang/mama 0.2.0-beta.2 → 0.2.0-beta.4
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/README.md +69 -41
- package/dist/adapter.d.ts +14 -4
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +8 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +252 -98
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +83 -21
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +71 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +168 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +5 -21
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +148 -150
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +21 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +96 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +92 -56
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +16 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +89 -103
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +40 -14
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +2 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +71 -142
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +3 -2
- package/dist/bindings.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +11 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +100 -16
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +61 -30
- package/dist/events.js.map +1 -1
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/{login.d.ts → login/index.d.ts} +1 -1
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +1 -1
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +5 -4
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1453 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/{link-token.d.ts → login/session.d.ts} +1 -1
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +1 -1
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +89 -19
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +17 -2
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +84 -5
- package/dist/provisioner.js.map +1 -1
- package/dist/session-policy.d.ts +13 -0
- package/dist/session-policy.d.ts.map +1 -0
- package/dist/session-policy.js +23 -0
- package/dist/session-policy.js.map +1 -0
- package/dist/session-store.d.ts +31 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +168 -6
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +11 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +11 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +795 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +416 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +16 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +38 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +3 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +15 -35
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +2 -0
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +21 -3
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +11 -55
- package/dist/vault.js.map +1 -1
- package/package.json +7 -8
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -899
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts.map +0 -1
- package/dist/link-token.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
package/dist/events.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync,
|
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import * as log from "./log.js";
|
|
6
|
+
import { inferConversationKind } from "./session-policy.js";
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// EventsWatcher
|
|
8
9
|
// ============================================================================
|
|
@@ -14,6 +15,7 @@ export class EventsWatcher {
|
|
|
14
15
|
this.eventsDir = eventsDir;
|
|
15
16
|
this.botsByPlatform = botsByPlatform;
|
|
16
17
|
this.timers = new Map();
|
|
18
|
+
this.timerEventTypes = new Map();
|
|
17
19
|
this.crons = new Map();
|
|
18
20
|
this.debounceTimers = new Map();
|
|
19
21
|
this.watcher = null;
|
|
@@ -32,9 +34,10 @@ export class EventsWatcher {
|
|
|
32
34
|
// Scan existing files
|
|
33
35
|
this.scanExisting();
|
|
34
36
|
// Watch for changes
|
|
35
|
-
this.watcher = watch(this.eventsDir, (
|
|
37
|
+
this.watcher = watch(this.eventsDir, (eventType, filename) => {
|
|
36
38
|
if (!filename || !filename.endsWith(".json"))
|
|
37
39
|
return;
|
|
40
|
+
log.logInfo(`Events watcher fs event: ${String(eventType)} ${filename} (exists=${existsSync(join(this.eventsDir, filename))})`);
|
|
38
41
|
this.debounce(filename, () => this.handleFileChange(filename));
|
|
39
42
|
});
|
|
40
43
|
log.logInfo(`Events watcher started, tracking ${this.knownFiles.size} files`);
|
|
@@ -58,6 +61,7 @@ export class EventsWatcher {
|
|
|
58
61
|
clearTimeout(timer);
|
|
59
62
|
}
|
|
60
63
|
this.timers.clear();
|
|
64
|
+
this.timerEventTypes.clear();
|
|
61
65
|
// Cancel all cron jobs
|
|
62
66
|
for (const cron of this.crons.values()) {
|
|
63
67
|
cron.stop();
|
|
@@ -122,34 +126,54 @@ export class EventsWatcher {
|
|
|
122
126
|
}
|
|
123
127
|
handleFileChange(filename) {
|
|
124
128
|
const filePath = join(this.eventsDir, filename);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const exists = existsSync(filePath);
|
|
130
|
+
const known = this.knownFiles.has(filename);
|
|
131
|
+
log.logInfo(`Handling event file change: ${filename} (exists=${exists}, known=${known})`);
|
|
132
|
+
if (!exists) {
|
|
133
|
+
// fs.watch can briefly report a file as missing during create/rename churn.
|
|
134
|
+
// Confirm deletion before canceling scheduled events.
|
|
135
|
+
void this.handleDelete(filename);
|
|
136
|
+
}
|
|
137
|
+
else if (known) {
|
|
130
138
|
// File was modified - cancel existing and re-schedule
|
|
131
|
-
this.cancelScheduled(filename);
|
|
132
|
-
this.handleFile(filename);
|
|
139
|
+
this.cancelScheduled(filename, "file-modified");
|
|
140
|
+
void this.handleFile(filename);
|
|
133
141
|
}
|
|
134
142
|
else {
|
|
135
143
|
// New file
|
|
136
|
-
this.handleFile(filename);
|
|
144
|
+
void this.handleFile(filename);
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
|
-
handleDelete(filename) {
|
|
147
|
+
async handleDelete(filename) {
|
|
140
148
|
if (!this.knownFiles.has(filename))
|
|
141
149
|
return;
|
|
150
|
+
const filePath = join(this.eventsDir, filename);
|
|
151
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
152
|
+
const delay = RETRY_BASE_MS * 2 ** i;
|
|
153
|
+
await this.sleep(delay);
|
|
154
|
+
const exists = existsSync(filePath);
|
|
155
|
+
log.logInfo(`Confirming event deletion: ${filename} after ${delay}ms (exists=${exists})`);
|
|
156
|
+
if (exists) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (this.timerEventTypes.get(filename) === "one-shot" && this.timers.has(filename)) {
|
|
161
|
+
log.logInfo(`Ignoring deleted one-shot file after scheduling: ${filename} (timer remains active)`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
142
164
|
log.logInfo(`Event file deleted: ${filename}`);
|
|
143
|
-
this.cancelScheduled(filename);
|
|
165
|
+
this.cancelScheduled(filename, "confirmed-delete");
|
|
144
166
|
this.knownFiles.delete(filename);
|
|
145
167
|
}
|
|
146
|
-
cancelScheduled(filename) {
|
|
168
|
+
cancelScheduled(filename, reason = "unspecified") {
|
|
147
169
|
const timer = this.timers.get(filename);
|
|
170
|
+
const cron = this.crons.get(filename);
|
|
171
|
+
log.logInfo(`Canceling scheduled event: ${filename} (reason=${reason}, timer=${Boolean(timer)}, cron=${Boolean(cron)})`);
|
|
148
172
|
if (timer) {
|
|
149
173
|
clearTimeout(timer);
|
|
150
174
|
this.timers.delete(filename);
|
|
175
|
+
this.timerEventTypes.delete(filename);
|
|
151
176
|
}
|
|
152
|
-
const cron = this.crons.get(filename);
|
|
153
177
|
if (cron) {
|
|
154
178
|
cron.stop();
|
|
155
179
|
this.crons.delete(filename);
|
|
@@ -157,6 +181,7 @@ export class EventsWatcher {
|
|
|
157
181
|
}
|
|
158
182
|
async handleFile(filename) {
|
|
159
183
|
const filePath = join(this.eventsDir, filename);
|
|
184
|
+
log.logInfo(`Loading event file: ${filename} from ${filePath}`);
|
|
160
185
|
// Parse with retries
|
|
161
186
|
let event = null;
|
|
162
187
|
let lastError = null;
|
|
@@ -175,10 +200,11 @@ export class EventsWatcher {
|
|
|
175
200
|
}
|
|
176
201
|
if (!event) {
|
|
177
202
|
log.logWarning(`Failed to parse event file after ${MAX_RETRIES} retries: ${filename}`, lastError?.message);
|
|
178
|
-
this.deleteFile(filename);
|
|
203
|
+
this.deleteFile(filename, "parse-failed");
|
|
179
204
|
return;
|
|
180
205
|
}
|
|
181
206
|
this.knownFiles.add(filename);
|
|
207
|
+
log.logInfo(`Parsed event file: ${filename} (${event.type} for ${event.platform}/${event.conversationId})`);
|
|
182
208
|
// Schedule based on type
|
|
183
209
|
switch (event.type) {
|
|
184
210
|
case "immediate":
|
|
@@ -205,6 +231,8 @@ export class EventsWatcher {
|
|
|
205
231
|
const platform = this.resolvePlatform(data.platform, filename);
|
|
206
232
|
const conversationKind = this.resolveConversationKind(platform, conversationId, data.conversationKind);
|
|
207
233
|
const userId = typeof data.userId === "string" ? data.userId : undefined;
|
|
234
|
+
const sessionKey = typeof data.sessionKey === "string" ? data.sessionKey : undefined;
|
|
235
|
+
const threadTs = typeof data.threadTs === "string" ? data.threadTs : undefined;
|
|
208
236
|
switch (data.type) {
|
|
209
237
|
case "immediate":
|
|
210
238
|
return {
|
|
@@ -214,6 +242,8 @@ export class EventsWatcher {
|
|
|
214
242
|
conversationKind,
|
|
215
243
|
userId,
|
|
216
244
|
text: data.text,
|
|
245
|
+
sessionKey,
|
|
246
|
+
threadTs,
|
|
217
247
|
};
|
|
218
248
|
case "one-shot":
|
|
219
249
|
if (!data.at) {
|
|
@@ -244,6 +274,7 @@ export class EventsWatcher {
|
|
|
244
274
|
text: data.text,
|
|
245
275
|
schedule: data.schedule,
|
|
246
276
|
timezone: data.timezone,
|
|
277
|
+
sessionKey,
|
|
247
278
|
};
|
|
248
279
|
default:
|
|
249
280
|
throw new Error(`Unknown event type '${data.type}' in ${filename}`);
|
|
@@ -267,13 +298,7 @@ export class EventsWatcher {
|
|
|
267
298
|
if (conversationKindValue === "direct" || conversationKindValue === "shared") {
|
|
268
299
|
return conversationKindValue;
|
|
269
300
|
}
|
|
270
|
-
|
|
271
|
-
return conversationId.startsWith("D") ? "direct" : "shared";
|
|
272
|
-
}
|
|
273
|
-
if (platform === "telegram") {
|
|
274
|
-
return conversationId.startsWith("-") ? "shared" : "direct";
|
|
275
|
-
}
|
|
276
|
-
return "shared";
|
|
301
|
+
return inferConversationKind(platform, conversationId);
|
|
277
302
|
}
|
|
278
303
|
handleImmediate(filename, event) {
|
|
279
304
|
const filePath = join(this.eventsDir, filename);
|
|
@@ -282,7 +307,7 @@ export class EventsWatcher {
|
|
|
282
307
|
const stat = statSync(filePath);
|
|
283
308
|
if (stat.mtimeMs < this.startTime) {
|
|
284
309
|
log.logInfo(`Stale immediate event, deleting: ${filename}`);
|
|
285
|
-
this.deleteFile(filename);
|
|
310
|
+
this.deleteFile(filename, "stale-immediate");
|
|
286
311
|
return;
|
|
287
312
|
}
|
|
288
313
|
}
|
|
@@ -299,17 +324,20 @@ export class EventsWatcher {
|
|
|
299
324
|
if (atTime <= now) {
|
|
300
325
|
// Past - delete without executing
|
|
301
326
|
log.logInfo(`One-shot event in the past, deleting: ${filename}`);
|
|
302
|
-
this.deleteFile(filename);
|
|
327
|
+
this.deleteFile(filename, "one-shot-in-past");
|
|
303
328
|
return;
|
|
304
329
|
}
|
|
305
330
|
const delay = atTime - now;
|
|
306
|
-
log.logInfo(`Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s`);
|
|
331
|
+
log.logInfo(`Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s (at=${event.at}, now=${new Date(now).toISOString()})`);
|
|
307
332
|
const timer = setTimeout(() => {
|
|
308
333
|
this.timers.delete(filename);
|
|
334
|
+
this.timerEventTypes.delete(filename);
|
|
309
335
|
log.logInfo(`Executing one-shot event: ${filename}`);
|
|
310
336
|
this.execute(filename, event);
|
|
311
337
|
}, delay);
|
|
312
338
|
this.timers.set(filename, timer);
|
|
339
|
+
this.timerEventTypes.set(filename, "one-shot");
|
|
340
|
+
log.logInfo(`Stored one-shot timer: ${filename} (active timers=${this.timers.size})`);
|
|
313
341
|
}
|
|
314
342
|
handlePeriodic(filename, event) {
|
|
315
343
|
try {
|
|
@@ -323,7 +351,7 @@ export class EventsWatcher {
|
|
|
323
351
|
}
|
|
324
352
|
catch (err) {
|
|
325
353
|
log.logWarning(`Invalid cron schedule for ${filename}: ${event.schedule}`, String(err));
|
|
326
|
-
this.deleteFile(filename);
|
|
354
|
+
this.deleteFile(filename, "invalid-cron");
|
|
327
355
|
}
|
|
328
356
|
}
|
|
329
357
|
execute(filename, event, deleteAfter = true) {
|
|
@@ -345,13 +373,14 @@ export class EventsWatcher {
|
|
|
345
373
|
if (!bot) {
|
|
346
374
|
log.logWarning(`No bot configured for event platform '${event.platform}'`, filename);
|
|
347
375
|
if (deleteAfter) {
|
|
348
|
-
this.deleteFile(filename);
|
|
376
|
+
this.deleteFile(filename, "missing-bot");
|
|
349
377
|
}
|
|
350
378
|
return;
|
|
351
379
|
}
|
|
352
380
|
// Create synthetic BotEvent. Keep a stable conversation session key so recurring
|
|
353
381
|
// reminders share context, but use a unique synthetic message id because
|
|
354
382
|
// some adapters treat ts/message id as a reply target.
|
|
383
|
+
const scopedEvent = event;
|
|
355
384
|
const syntheticEvent = {
|
|
356
385
|
type: "mention",
|
|
357
386
|
conversationId: event.conversationId,
|
|
@@ -359,24 +388,26 @@ export class EventsWatcher {
|
|
|
359
388
|
user: event.userId ?? "EVENT",
|
|
360
389
|
text: message,
|
|
361
390
|
ts: `event:${filename}`,
|
|
362
|
-
|
|
391
|
+
thread_ts: scopedEvent.threadTs,
|
|
392
|
+
sessionKey: scopedEvent.sessionKey ?? event.conversationId,
|
|
363
393
|
};
|
|
364
394
|
// Enqueue for processing
|
|
365
395
|
const enqueued = bot.enqueueEvent(syntheticEvent);
|
|
366
396
|
if (enqueued && deleteAfter) {
|
|
367
397
|
// Delete file after successful enqueue (immediate and one-shot)
|
|
368
|
-
this.deleteFile(filename);
|
|
398
|
+
this.deleteFile(filename, "executed-and-enqueued");
|
|
369
399
|
}
|
|
370
400
|
else if (!enqueued) {
|
|
371
401
|
log.logWarning(`Event queue full, discarded: ${filename}`);
|
|
372
402
|
// Still delete immediate/one-shot even if discarded
|
|
373
403
|
if (deleteAfter) {
|
|
374
|
-
this.deleteFile(filename);
|
|
404
|
+
this.deleteFile(filename, "queue-full-discarded");
|
|
375
405
|
}
|
|
376
406
|
}
|
|
377
407
|
}
|
|
378
|
-
deleteFile(filename) {
|
|
408
|
+
deleteFile(filename, reason = "unspecified") {
|
|
379
409
|
const filePath = join(this.eventsDir, filename);
|
|
410
|
+
log.logInfo(`Deleting event file: ${filename} (reason=${reason})`);
|
|
380
411
|
try {
|
|
381
412
|
unlinkSync(filePath);
|
|
382
413
|
}
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["import { Cron } from \"croner\";\nimport {\n existsSync,\n type FSWatcher,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n unlinkSync,\n watch,\n} from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport type { Bot, BotEvent, ConversationKind } from \"./adapter.js\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface ImmediateEvent {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n /** Creator userId — routes tool execution to that user's vault selection when fired. */\n userId?: string;\n text: string;\n}\n\nexport interface OneShotEvent {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n userId?: string;\n text: string;\n at: string; // ISO 8601 with timezone offset\n}\n\nexport interface PeriodicEvent {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n userId?: string;\n text: string;\n schedule: string; // cron syntax\n timezone: string; // IANA timezone\n}\n\nexport type MamaEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;\n\nexport interface PeriodicEventInfo {\n filename: string;\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n text: string;\n schedule: string;\n timezone: string;\n nextRun: string | null; // ISO 8601\n}\n\n// ============================================================================\n// EventsWatcher\n// ============================================================================\n\nconst DEBOUNCE_MS = 100;\nconst MAX_RETRIES = 3;\nconst RETRY_BASE_MS = 100;\n\nexport class EventsWatcher {\n private timers: Map<string, NodeJS.Timeout> = new Map();\n private crons: Map<string, Cron> = new Map();\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n private startTime: number;\n private watcher: FSWatcher | null = null;\n private knownFiles: Set<string> = new Set();\n\n constructor(\n private eventsDir: string,\n private botsByPlatform: Record<string, Bot>,\n ) {\n this.startTime = Date.now();\n }\n\n /**\n * Start watching for events. Call this after platform bots are initialized.\n */\n start(): void {\n // Ensure events directory exists\n if (!existsSync(this.eventsDir)) {\n mkdirSync(this.eventsDir, { recursive: true });\n }\n\n log.logInfo(`Events watcher starting, dir: ${this.eventsDir}`);\n\n // Scan existing files\n this.scanExisting();\n\n // Watch for changes\n this.watcher = watch(this.eventsDir, (_eventType, filename) => {\n if (!filename || !filename.endsWith(\".json\")) return;\n this.debounce(filename, () => this.handleFileChange(filename));\n });\n\n log.logInfo(`Events watcher started, tracking ${this.knownFiles.size} files`);\n }\n\n /**\n * Stop watching and cancel all scheduled events.\n */\n stop(): void {\n // Stop fs watcher\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n\n // Cancel all debounce timers\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer);\n }\n this.debounceTimers.clear();\n\n // Cancel all scheduled timers\n for (const timer of this.timers.values()) {\n clearTimeout(timer);\n }\n this.timers.clear();\n\n // Cancel all cron jobs\n for (const cron of this.crons.values()) {\n cron.stop();\n }\n this.crons.clear();\n\n this.knownFiles.clear();\n log.logInfo(\"Events watcher stopped\");\n }\n\n /**\n * Return all active periodic (cron) events with their next run time.\n */\n getPeriodicEvents(): PeriodicEventInfo[] {\n const results: PeriodicEventInfo[] = [];\n for (const [filename, cron] of this.crons) {\n const filePath = join(this.eventsDir, filename);\n try {\n const content = readFileSync(filePath, \"utf-8\");\n const data = this.parseEvent(content, filename);\n if (!data || data.type !== \"periodic\") {\n continue;\n }\n const next = cron.nextRun();\n results.push({\n filename,\n platform: data.platform,\n conversationId: data.conversationId,\n conversationKind: data.conversationKind,\n text: data.text,\n schedule: data.schedule,\n timezone: data.timezone,\n nextRun: next?.toISOString() ?? null,\n });\n } catch {\n // File may have been deleted or corrupted, skip\n }\n }\n return results;\n }\n\n private debounce(filename: string, fn: () => void): void {\n const existing = this.debounceTimers.get(filename);\n if (existing) {\n clearTimeout(existing);\n }\n this.debounceTimers.set(\n filename,\n setTimeout(() => {\n this.debounceTimers.delete(filename);\n fn();\n }, DEBOUNCE_MS),\n );\n }\n\n private scanExisting(): void {\n let files: string[];\n try {\n files = readdirSync(this.eventsDir).filter((f) => f.endsWith(\".json\"));\n } catch (err) {\n log.logWarning(\"Failed to read events directory\", String(err));\n return;\n }\n\n for (const filename of files) {\n this.handleFile(filename);\n }\n }\n\n private handleFileChange(filename: string): void {\n const filePath = join(this.eventsDir, filename);\n\n if (!existsSync(filePath)) {\n // File was deleted\n this.handleDelete(filename);\n } else if (this.knownFiles.has(filename)) {\n // File was modified - cancel existing and re-schedule\n this.cancelScheduled(filename);\n this.handleFile(filename);\n } else {\n // New file\n this.handleFile(filename);\n }\n }\n\n private handleDelete(filename: string): void {\n if (!this.knownFiles.has(filename)) return;\n\n log.logInfo(`Event file deleted: ${filename}`);\n this.cancelScheduled(filename);\n this.knownFiles.delete(filename);\n }\n\n private cancelScheduled(filename: string): void {\n const timer = this.timers.get(filename);\n if (timer) {\n clearTimeout(timer);\n this.timers.delete(filename);\n }\n\n const cron = this.crons.get(filename);\n if (cron) {\n cron.stop();\n this.crons.delete(filename);\n }\n }\n\n private async handleFile(filename: string): Promise<void> {\n const filePath = join(this.eventsDir, filename);\n\n // Parse with retries\n let event: MamaEvent | null = null;\n let lastError: Error | null = null;\n\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const content = await readFile(filePath, \"utf-8\");\n event = this.parseEvent(content, filename);\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (i < MAX_RETRIES - 1) {\n await this.sleep(RETRY_BASE_MS * 2 ** i);\n }\n }\n }\n\n if (!event) {\n log.logWarning(\n `Failed to parse event file after ${MAX_RETRIES} retries: ${filename}`,\n lastError?.message,\n );\n this.deleteFile(filename);\n return;\n }\n\n this.knownFiles.add(filename);\n\n // Schedule based on type\n switch (event.type) {\n case \"immediate\":\n this.handleImmediate(filename, event);\n break;\n case \"one-shot\":\n this.handleOneShot(filename, event);\n break;\n case \"periodic\":\n this.handlePeriodic(filename, event);\n break;\n }\n }\n\n private parseEvent(content: string, filename: string): MamaEvent | null {\n const data = JSON.parse(content);\n const conversationId =\n typeof data.conversationId === \"string\"\n ? data.conversationId\n : typeof data.channelId === \"string\"\n ? data.channelId\n : undefined;\n\n if (!data.type || !conversationId || !data.text) {\n throw new Error(`Missing required fields (type, conversationId, text) in ${filename}`);\n }\n\n const platform = this.resolvePlatform(data.platform, filename);\n const conversationKind = this.resolveConversationKind(\n platform,\n conversationId,\n data.conversationKind,\n );\n const userId = typeof data.userId === \"string\" ? data.userId : undefined;\n\n switch (data.type) {\n case \"immediate\":\n return {\n type: \"immediate\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n };\n\n case \"one-shot\":\n if (!data.at) {\n throw new Error(`Missing 'at' field for one-shot event in ${filename}`);\n }\n return {\n type: \"one-shot\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n at: data.at,\n };\n\n case \"periodic\":\n if (!data.schedule) {\n throw new Error(`Missing 'schedule' field for periodic event in ${filename}`);\n }\n if (!data.timezone) {\n throw new Error(`Missing 'timezone' field for periodic event in ${filename}`);\n }\n return {\n type: \"periodic\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n schedule: data.schedule,\n timezone: data.timezone,\n };\n\n default:\n throw new Error(`Unknown event type '${data.type}' in ${filename}`);\n }\n }\n\n private resolvePlatform(platformValue: unknown, filename: string): string {\n const availablePlatforms = Object.keys(this.botsByPlatform);\n\n if (typeof platformValue === \"string\" && platformValue.trim().length > 0) {\n const platform = platformValue.trim().toLowerCase();\n if (!this.botsByPlatform[platform]) {\n throw new Error(\n `Unknown platform '${platformValue}' in ${filename}. Expected one of: ${availablePlatforms.join(\", \")}`,\n );\n }\n return platform;\n }\n\n if (availablePlatforms.length === 1) {\n return availablePlatforms[0];\n }\n\n throw new Error(\n `Missing required field 'platform' in ${filename}. Available platforms: ${availablePlatforms.join(\", \")}`,\n );\n }\n\n private resolveConversationKind(\n platform: string,\n conversationId: string,\n conversationKindValue: unknown,\n ): ConversationKind {\n if (conversationKindValue === \"direct\" || conversationKindValue === \"shared\") {\n return conversationKindValue;\n }\n\n if (platform === \"slack\") {\n return conversationId.startsWith(\"D\") ? \"direct\" : \"shared\";\n }\n\n if (platform === \"telegram\") {\n return conversationId.startsWith(\"-\") ? \"shared\" : \"direct\";\n }\n\n return \"shared\";\n }\n\n private handleImmediate(filename: string, event: ImmediateEvent): void {\n const filePath = join(this.eventsDir, filename);\n\n // Check if stale (created before harness started)\n try {\n const stat = statSync(filePath);\n if (stat.mtimeMs < this.startTime) {\n log.logInfo(`Stale immediate event, deleting: ${filename}`);\n this.deleteFile(filename);\n return;\n }\n } catch {\n // File may have been deleted\n return;\n }\n\n log.logInfo(`Executing immediate event: ${filename}`);\n this.execute(filename, event);\n }\n\n private handleOneShot(filename: string, event: OneShotEvent): void {\n const atTime = new Date(event.at).getTime();\n const now = Date.now();\n\n if (atTime <= now) {\n // Past - delete without executing\n log.logInfo(`One-shot event in the past, deleting: ${filename}`);\n this.deleteFile(filename);\n return;\n }\n\n const delay = atTime - now;\n log.logInfo(`Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s`);\n\n const timer = setTimeout(() => {\n this.timers.delete(filename);\n log.logInfo(`Executing one-shot event: ${filename}`);\n this.execute(filename, event);\n }, delay);\n\n this.timers.set(filename, timer);\n }\n\n private handlePeriodic(filename: string, event: PeriodicEvent): void {\n try {\n const cron = new Cron(event.schedule, { timezone: event.timezone }, () => {\n log.logInfo(`Executing periodic event: ${filename}`);\n this.execute(filename, event, false); // Don't delete periodic events\n });\n\n this.crons.set(filename, cron);\n\n const next = cron.nextRun();\n log.logInfo(\n `Scheduled periodic event: ${filename}, next run: ${next?.toISOString() ?? \"unknown\"}`,\n );\n } catch (err) {\n log.logWarning(`Invalid cron schedule for ${filename}: ${event.schedule}`, String(err));\n this.deleteFile(filename);\n }\n }\n\n private execute(filename: string, event: MamaEvent, deleteAfter: boolean = true): void {\n // Format the message\n let scheduleInfo: string;\n switch (event.type) {\n case \"immediate\":\n scheduleInfo = \"immediate\";\n break;\n case \"one-shot\":\n scheduleInfo = event.at;\n break;\n case \"periodic\":\n scheduleInfo = event.schedule;\n break;\n }\n\n const message = `[EVENT:${filename}:${event.type}:${scheduleInfo}] ${event.text}`;\n const bot = this.botsByPlatform[event.platform];\n\n if (!bot) {\n log.logWarning(`No bot configured for event platform '${event.platform}'`, filename);\n if (deleteAfter) {\n this.deleteFile(filename);\n }\n return;\n }\n\n // Create synthetic BotEvent. Keep a stable conversation session key so recurring\n // reminders share context, but use a unique synthetic message id because\n // some adapters treat ts/message id as a reply target.\n const syntheticEvent: BotEvent = {\n type: \"mention\",\n conversationId: event.conversationId,\n conversationKind: event.conversationKind,\n user: event.userId ?? \"EVENT\",\n text: message,\n ts: `event:${filename}`,\n sessionKey: event.conversationId,\n };\n\n // Enqueue for processing\n const enqueued = bot.enqueueEvent(syntheticEvent);\n\n if (enqueued && deleteAfter) {\n // Delete file after successful enqueue (immediate and one-shot)\n this.deleteFile(filename);\n } else if (!enqueued) {\n log.logWarning(`Event queue full, discarded: ${filename}`);\n // Still delete immediate/one-shot even if discarded\n if (deleteAfter) {\n this.deleteFile(filename);\n }\n }\n }\n\n private deleteFile(filename: string): void {\n const filePath = join(this.eventsDir, filename);\n try {\n unlinkSync(filePath);\n } catch (err) {\n // ENOENT is fine (file already deleted), other errors are warnings\n if (err instanceof Error && \"code\" in err && err.code !== \"ENOENT\") {\n log.logWarning(`Failed to delete event file: ${filename}`, String(err));\n }\n }\n this.knownFiles.delete(filename);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create an events watcher for all configured platforms.\n */\nexport function createEventsWatcher(\n workspaceDir: string,\n botsByPlatform: Record<string, Bot>,\n): EventsWatcher {\n const eventsDir = join(workspaceDir, \"events\");\n return new EventsWatcher(eventsDir, botsByPlatform);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["import { Cron } from \"croner\";\nimport {\n existsSync,\n type FSWatcher,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n unlinkSync,\n watch,\n} from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport type { Bot, BotEvent, ConversationKind } from \"./adapter.js\";\nimport * as log from \"./log.js\";\nimport { inferConversationKind } from \"./session-policy.js\";\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface ImmediateEvent {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n /** Creator userId — routes tool execution to that user's vault selection when fired. */\n userId?: string;\n text: string;\n /** Determines which AgentRunner handles the event. */\n sessionKey?: string;\n /** Sub-conversation target (Slack thread ts, Discord thread id, Telegram reply-to id). */\n threadTs?: string;\n}\n\nexport interface OneShotEvent {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n userId?: string;\n text: string;\n at: string; // ISO 8601 with timezone offset\n // No sessionKey or threadTs: reminders fire as top-level messages regardless of where they were created.\n}\n\nexport interface PeriodicEvent {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n userId?: string;\n text: string;\n schedule: string; // cron syntax\n timezone: string; // IANA timezone\n /** Determines which AgentRunner handles the event. */\n sessionKey?: string;\n // No threadTs: recurring events always fire as top-level messages.\n}\n\nexport type MamaEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;\n\nexport interface PeriodicEventInfo {\n filename: string;\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n text: string;\n schedule: string;\n timezone: string;\n nextRun: string | null; // ISO 8601\n}\n\n// ============================================================================\n// EventsWatcher\n// ============================================================================\n\nconst DEBOUNCE_MS = 100;\nconst MAX_RETRIES = 3;\nconst RETRY_BASE_MS = 100;\n\nexport class EventsWatcher {\n private timers: Map<string, NodeJS.Timeout> = new Map();\n private timerEventTypes: Map<string, \"one-shot\"> = new Map();\n private crons: Map<string, Cron> = new Map();\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n private startTime: number;\n private watcher: FSWatcher | null = null;\n private knownFiles: Set<string> = new Set();\n\n constructor(\n private eventsDir: string,\n private botsByPlatform: Record<string, Bot>,\n ) {\n this.startTime = Date.now();\n }\n\n /**\n * Start watching for events. Call this after platform bots are initialized.\n */\n start(): void {\n // Ensure events directory exists\n if (!existsSync(this.eventsDir)) {\n mkdirSync(this.eventsDir, { recursive: true });\n }\n\n log.logInfo(`Events watcher starting, dir: ${this.eventsDir}`);\n\n // Scan existing files\n this.scanExisting();\n\n // Watch for changes\n this.watcher = watch(this.eventsDir, (eventType, filename) => {\n if (!filename || !filename.endsWith(\".json\")) return;\n log.logInfo(\n `Events watcher fs event: ${String(eventType)} ${filename} (exists=${existsSync(join(this.eventsDir, filename))})`,\n );\n this.debounce(filename, () => this.handleFileChange(filename));\n });\n\n log.logInfo(`Events watcher started, tracking ${this.knownFiles.size} files`);\n }\n\n /**\n * Stop watching and cancel all scheduled events.\n */\n stop(): void {\n // Stop fs watcher\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n\n // Cancel all debounce timers\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer);\n }\n this.debounceTimers.clear();\n\n // Cancel all scheduled timers\n for (const timer of this.timers.values()) {\n clearTimeout(timer);\n }\n this.timers.clear();\n this.timerEventTypes.clear();\n\n // Cancel all cron jobs\n for (const cron of this.crons.values()) {\n cron.stop();\n }\n this.crons.clear();\n\n this.knownFiles.clear();\n log.logInfo(\"Events watcher stopped\");\n }\n\n /**\n * Return all active periodic (cron) events with their next run time.\n */\n getPeriodicEvents(): PeriodicEventInfo[] {\n const results: PeriodicEventInfo[] = [];\n for (const [filename, cron] of this.crons) {\n const filePath = join(this.eventsDir, filename);\n try {\n const content = readFileSync(filePath, \"utf-8\");\n const data = this.parseEvent(content, filename);\n if (!data || data.type !== \"periodic\") {\n continue;\n }\n const next = cron.nextRun();\n results.push({\n filename,\n platform: data.platform,\n conversationId: data.conversationId,\n conversationKind: data.conversationKind,\n text: data.text,\n schedule: data.schedule,\n timezone: data.timezone,\n nextRun: next?.toISOString() ?? null,\n });\n } catch {\n // File may have been deleted or corrupted, skip\n }\n }\n return results;\n }\n\n private debounce(filename: string, fn: () => void): void {\n const existing = this.debounceTimers.get(filename);\n if (existing) {\n clearTimeout(existing);\n }\n this.debounceTimers.set(\n filename,\n setTimeout(() => {\n this.debounceTimers.delete(filename);\n fn();\n }, DEBOUNCE_MS),\n );\n }\n\n private scanExisting(): void {\n let files: string[];\n try {\n files = readdirSync(this.eventsDir).filter((f) => f.endsWith(\".json\"));\n } catch (err) {\n log.logWarning(\"Failed to read events directory\", String(err));\n return;\n }\n\n for (const filename of files) {\n this.handleFile(filename);\n }\n }\n\n private handleFileChange(filename: string): void {\n const filePath = join(this.eventsDir, filename);\n const exists = existsSync(filePath);\n const known = this.knownFiles.has(filename);\n log.logInfo(`Handling event file change: ${filename} (exists=${exists}, known=${known})`);\n\n if (!exists) {\n // fs.watch can briefly report a file as missing during create/rename churn.\n // Confirm deletion before canceling scheduled events.\n void this.handleDelete(filename);\n } else if (known) {\n // File was modified - cancel existing and re-schedule\n this.cancelScheduled(filename, \"file-modified\");\n void this.handleFile(filename);\n } else {\n // New file\n void this.handleFile(filename);\n }\n }\n\n private async handleDelete(filename: string): Promise<void> {\n if (!this.knownFiles.has(filename)) return;\n\n const filePath = join(this.eventsDir, filename);\n for (let i = 0; i < MAX_RETRIES; i++) {\n const delay = RETRY_BASE_MS * 2 ** i;\n await this.sleep(delay);\n const exists = existsSync(filePath);\n log.logInfo(`Confirming event deletion: ${filename} after ${delay}ms (exists=${exists})`);\n if (exists) {\n return;\n }\n }\n\n if (this.timerEventTypes.get(filename) === \"one-shot\" && this.timers.has(filename)) {\n log.logInfo(\n `Ignoring deleted one-shot file after scheduling: ${filename} (timer remains active)`,\n );\n return;\n }\n\n log.logInfo(`Event file deleted: ${filename}`);\n this.cancelScheduled(filename, \"confirmed-delete\");\n this.knownFiles.delete(filename);\n }\n\n private cancelScheduled(filename: string, reason = \"unspecified\"): void {\n const timer = this.timers.get(filename);\n const cron = this.crons.get(filename);\n log.logInfo(\n `Canceling scheduled event: ${filename} (reason=${reason}, timer=${Boolean(timer)}, cron=${Boolean(cron)})`,\n );\n if (timer) {\n clearTimeout(timer);\n this.timers.delete(filename);\n this.timerEventTypes.delete(filename);\n }\n\n if (cron) {\n cron.stop();\n this.crons.delete(filename);\n }\n }\n\n private async handleFile(filename: string): Promise<void> {\n const filePath = join(this.eventsDir, filename);\n log.logInfo(`Loading event file: ${filename} from ${filePath}`);\n\n // Parse with retries\n let event: MamaEvent | null = null;\n let lastError: Error | null = null;\n\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const content = await readFile(filePath, \"utf-8\");\n event = this.parseEvent(content, filename);\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (i < MAX_RETRIES - 1) {\n await this.sleep(RETRY_BASE_MS * 2 ** i);\n }\n }\n }\n\n if (!event) {\n log.logWarning(\n `Failed to parse event file after ${MAX_RETRIES} retries: ${filename}`,\n lastError?.message,\n );\n this.deleteFile(filename, \"parse-failed\");\n return;\n }\n\n this.knownFiles.add(filename);\n log.logInfo(\n `Parsed event file: ${filename} (${event.type} for ${event.platform}/${event.conversationId})`,\n );\n\n // Schedule based on type\n switch (event.type) {\n case \"immediate\":\n this.handleImmediate(filename, event);\n break;\n case \"one-shot\":\n this.handleOneShot(filename, event);\n break;\n case \"periodic\":\n this.handlePeriodic(filename, event);\n break;\n }\n }\n\n private parseEvent(content: string, filename: string): MamaEvent | null {\n const data = JSON.parse(content);\n const conversationId =\n typeof data.conversationId === \"string\"\n ? data.conversationId\n : typeof data.channelId === \"string\"\n ? data.channelId\n : undefined;\n\n if (!data.type || !conversationId || !data.text) {\n throw new Error(`Missing required fields (type, conversationId, text) in ${filename}`);\n }\n\n const platform = this.resolvePlatform(data.platform, filename);\n const conversationKind = this.resolveConversationKind(\n platform,\n conversationId,\n data.conversationKind,\n );\n const userId = typeof data.userId === \"string\" ? data.userId : undefined;\n const sessionKey = typeof data.sessionKey === \"string\" ? data.sessionKey : undefined;\n const threadTs = typeof data.threadTs === \"string\" ? data.threadTs : undefined;\n\n switch (data.type) {\n case \"immediate\":\n return {\n type: \"immediate\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n sessionKey,\n threadTs,\n };\n\n case \"one-shot\":\n if (!data.at) {\n throw new Error(`Missing 'at' field for one-shot event in ${filename}`);\n }\n return {\n type: \"one-shot\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n at: data.at,\n };\n\n case \"periodic\":\n if (!data.schedule) {\n throw new Error(`Missing 'schedule' field for periodic event in ${filename}`);\n }\n if (!data.timezone) {\n throw new Error(`Missing 'timezone' field for periodic event in ${filename}`);\n }\n return {\n type: \"periodic\",\n platform,\n conversationId,\n conversationKind,\n userId,\n text: data.text,\n schedule: data.schedule,\n timezone: data.timezone,\n sessionKey,\n };\n\n default:\n throw new Error(`Unknown event type '${data.type}' in ${filename}`);\n }\n }\n\n private resolvePlatform(platformValue: unknown, filename: string): string {\n const availablePlatforms = Object.keys(this.botsByPlatform);\n\n if (typeof platformValue === \"string\" && platformValue.trim().length > 0) {\n const platform = platformValue.trim().toLowerCase();\n if (!this.botsByPlatform[platform]) {\n throw new Error(\n `Unknown platform '${platformValue}' in ${filename}. Expected one of: ${availablePlatforms.join(\", \")}`,\n );\n }\n return platform;\n }\n\n if (availablePlatforms.length === 1) {\n return availablePlatforms[0];\n }\n\n throw new Error(\n `Missing required field 'platform' in ${filename}. Available platforms: ${availablePlatforms.join(\", \")}`,\n );\n }\n\n private resolveConversationKind(\n platform: string,\n conversationId: string,\n conversationKindValue: unknown,\n ): ConversationKind {\n if (conversationKindValue === \"direct\" || conversationKindValue === \"shared\") {\n return conversationKindValue;\n }\n\n return inferConversationKind(platform, conversationId);\n }\n\n private handleImmediate(filename: string, event: ImmediateEvent): void {\n const filePath = join(this.eventsDir, filename);\n\n // Check if stale (created before harness started)\n try {\n const stat = statSync(filePath);\n if (stat.mtimeMs < this.startTime) {\n log.logInfo(`Stale immediate event, deleting: ${filename}`);\n this.deleteFile(filename, \"stale-immediate\");\n return;\n }\n } catch {\n // File may have been deleted\n return;\n }\n\n log.logInfo(`Executing immediate event: ${filename}`);\n this.execute(filename, event);\n }\n\n private handleOneShot(filename: string, event: OneShotEvent): void {\n const atTime = new Date(event.at).getTime();\n const now = Date.now();\n\n if (atTime <= now) {\n // Past - delete without executing\n log.logInfo(`One-shot event in the past, deleting: ${filename}`);\n this.deleteFile(filename, \"one-shot-in-past\");\n return;\n }\n\n const delay = atTime - now;\n log.logInfo(\n `Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s (at=${event.at}, now=${new Date(now).toISOString()})`,\n );\n\n const timer = setTimeout(() => {\n this.timers.delete(filename);\n this.timerEventTypes.delete(filename);\n log.logInfo(`Executing one-shot event: ${filename}`);\n this.execute(filename, event);\n }, delay);\n\n this.timers.set(filename, timer);\n this.timerEventTypes.set(filename, \"one-shot\");\n log.logInfo(`Stored one-shot timer: ${filename} (active timers=${this.timers.size})`);\n }\n\n private handlePeriodic(filename: string, event: PeriodicEvent): void {\n try {\n const cron = new Cron(event.schedule, { timezone: event.timezone }, () => {\n log.logInfo(`Executing periodic event: ${filename}`);\n this.execute(filename, event, false); // Don't delete periodic events\n });\n\n this.crons.set(filename, cron);\n\n const next = cron.nextRun();\n log.logInfo(\n `Scheduled periodic event: ${filename}, next run: ${next?.toISOString() ?? \"unknown\"}`,\n );\n } catch (err) {\n log.logWarning(`Invalid cron schedule for ${filename}: ${event.schedule}`, String(err));\n this.deleteFile(filename, \"invalid-cron\");\n }\n }\n\n private execute(filename: string, event: MamaEvent, deleteAfter: boolean = true): void {\n // Format the message\n let scheduleInfo: string;\n switch (event.type) {\n case \"immediate\":\n scheduleInfo = \"immediate\";\n break;\n case \"one-shot\":\n scheduleInfo = event.at;\n break;\n case \"periodic\":\n scheduleInfo = event.schedule;\n break;\n }\n\n const message = `[EVENT:${filename}:${event.type}:${scheduleInfo}] ${event.text}`;\n const bot = this.botsByPlatform[event.platform];\n\n if (!bot) {\n log.logWarning(`No bot configured for event platform '${event.platform}'`, filename);\n if (deleteAfter) {\n this.deleteFile(filename, \"missing-bot\");\n }\n return;\n }\n\n // Create synthetic BotEvent. Keep a stable conversation session key so recurring\n // reminders share context, but use a unique synthetic message id because\n // some adapters treat ts/message id as a reply target.\n const scopedEvent = event as { sessionKey?: string; threadTs?: string };\n const syntheticEvent: BotEvent = {\n type: \"mention\",\n conversationId: event.conversationId,\n conversationKind: event.conversationKind,\n user: event.userId ?? \"EVENT\",\n text: message,\n ts: `event:${filename}`,\n thread_ts: scopedEvent.threadTs,\n sessionKey: scopedEvent.sessionKey ?? event.conversationId,\n };\n\n // Enqueue for processing\n const enqueued = bot.enqueueEvent(syntheticEvent);\n\n if (enqueued && deleteAfter) {\n // Delete file after successful enqueue (immediate and one-shot)\n this.deleteFile(filename, \"executed-and-enqueued\");\n } else if (!enqueued) {\n log.logWarning(`Event queue full, discarded: ${filename}`);\n // Still delete immediate/one-shot even if discarded\n if (deleteAfter) {\n this.deleteFile(filename, \"queue-full-discarded\");\n }\n }\n }\n\n private deleteFile(filename: string, reason = \"unspecified\"): void {\n const filePath = join(this.eventsDir, filename);\n log.logInfo(`Deleting event file: ${filename} (reason=${reason})`);\n try {\n unlinkSync(filePath);\n } catch (err) {\n // ENOENT is fine (file already deleted), other errors are warnings\n if (err instanceof Error && \"code\" in err && err.code !== \"ENOENT\") {\n log.logWarning(`Failed to delete event file: ${filename}`, String(err));\n }\n }\n this.knownFiles.delete(filename);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create an events watcher for all configured platforms.\n */\nexport function createEventsWatcher(\n workspaceDir: string,\n botsByPlatform: Record<string, Bot>,\n): EventsWatcher {\n const eventsDir = join(workspaceDir, \"events\");\n return new EventsWatcher(eventsDir, botsByPlatform);\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `content` to `targetPath` with mode 0600, even when `targetPath`
|
|
3
|
+
* already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel
|
|
4
|
+
* guarantees permissions at creation, not after a racy chmod) and then
|
|
5
|
+
* rename(2) into place for atomicity. Readers never see a torn write,
|
|
6
|
+
* and a crash mid-write leaves either the old file or a stray .tmp
|
|
7
|
+
* (cleaned by the next attempt or manually) — never a half-written target.
|
|
8
|
+
*/
|
|
9
|
+
export declare function atomicWritePrivateFile(targetPath: string, content: string): void;
|
|
10
|
+
//# sourceMappingURL=fs-atomic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.d.ts","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAiChF","sourcesContent":["import {\n closeSync,\n constants as fsConstants,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"fs\";\nimport { randomBytes } from \"crypto\";\nimport { basename, dirname, join } from \"path\";\n\nconst PRIVATE_FILE_MODE = 0o600;\n\n/**\n * Write `content` to `targetPath` with mode 0600, even when `targetPath`\n * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel\n * guarantees permissions at creation, not after a racy chmod) and then\n * rename(2) into place for atomicity. Readers never see a torn write,\n * and a crash mid-write leaves either the old file or a stray .tmp\n * (cleaned by the next attempt or manually) — never a half-written target.\n */\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore — original error is more informative\n }\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { closeSync, constants as fsConstants, openSync, renameSync, unlinkSync, writeSync, } from "fs";
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
import { basename, dirname, join } from "path";
|
|
4
|
+
const PRIVATE_FILE_MODE = 0o600;
|
|
5
|
+
/**
|
|
6
|
+
* Write `content` to `targetPath` with mode 0600, even when `targetPath`
|
|
7
|
+
* already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel
|
|
8
|
+
* guarantees permissions at creation, not after a racy chmod) and then
|
|
9
|
+
* rename(2) into place for atomicity. Readers never see a torn write,
|
|
10
|
+
* and a crash mid-write leaves either the old file or a stray .tmp
|
|
11
|
+
* (cleaned by the next attempt or manually) — never a half-written target.
|
|
12
|
+
*/
|
|
13
|
+
export function atomicWritePrivateFile(targetPath, content) {
|
|
14
|
+
const dir = dirname(targetPath);
|
|
15
|
+
const tmpPath = join(dir, `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
|
|
16
|
+
const fd = openSync(tmpPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, PRIVATE_FILE_MODE);
|
|
17
|
+
try {
|
|
18
|
+
writeSync(fd, content);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
try {
|
|
22
|
+
unlinkSync(tmpPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore — original error is more informative
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
closeSync(fd);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
renameSync(tmpPath, targetPath);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(tmpPath);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=fs-atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.js","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,IAAI,WAAW,EACxB,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE/C,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB,EAAE,OAAe;IACxE,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAClB,GAAG,EACH,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAChF,CAAC;IACF,MAAM,EAAE,GAAG,QAAQ,CACjB,OAAO,EACP,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,MAAM,EAC/D,iBAAiB,CAClB,CAAC;IACF,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["import {\n closeSync,\n constants as fsConstants,\n openSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"fs\";\nimport { randomBytes } from \"crypto\";\nimport { basename, dirname, join } from \"path\";\n\nconst PRIVATE_FILE_MODE = 0o600;\n\n/**\n * Write `content` to `targetPath` with mode 0600, even when `targetPath`\n * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel\n * guarantees permissions at creation, not after a racy chmod) and then\n * rename(2) into place for atomicity. Readers never see a torn write,\n * and a crash mid-write leaves either the old file or a stray .tmp\n * (cleaned by the next attempt or manually) — never a half-written target.\n */\nexport function atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore — original error is more informative\n }\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n"]}
|
|
@@ -26,4 +26,4 @@ export interface ParsedLoginCommand {
|
|
|
26
26
|
export declare function getOAuthServices(): OAuthService[];
|
|
27
27
|
export declare function resolveOAuthService(input: string): OAuthService | undefined;
|
|
28
28
|
export declare function parseLoginCommand(text: string): ParsedLoginCommand | null;
|
|
29
|
-
//# sourceMappingURL=
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,iBAAiB,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;IACxC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,6BAA6B,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;CAC3C;AA+ED,wBAAgB,gBAAgB,IAAI,YAAY,EAAE,CA6FjD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAM3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAUzE","sourcesContent":["export type LoginCredentialKind = \"api_key\" | \"oauth\";\n\nexport interface OAuthAuthorizedUserFileOutput {\n type: \"authorized_user\";\n relativePath: string;\n targetPath?: string;\n envKey?: string;\n}\n\nexport interface OAuthService {\n id: string;\n label: string;\n aliases: string[];\n authorizationUrl: string;\n tokenUrl: string;\n scopes: string[];\n clientIdEnvKey: string;\n clientSecretEnvKey: string;\n accessTokenEnvKey?: string;\n additionalAccessTokenEnvKeys?: string[];\n refreshTokenEnvKey?: string;\n authorizationParams?: Record<string, string>;\n fileOutput?: OAuthAuthorizedUserFileOutput;\n}\n\nexport interface ParsedLoginCommand {\n command: \"login\" | \"/login\" | \"/pi-login\";\n}\n\nconst DEFAULT_GOOGLE_WORKSPACE_CLI_SCOPES = [\n \"https://www.googleapis.com/auth/drive\",\n \"https://mail.google.com/\",\n \"https://www.googleapis.com/auth/calendar\",\n \"https://www.googleapis.com/auth/spreadsheets\",\n \"https://www.googleapis.com/auth/documents\",\n \"https://www.googleapis.com/auth/chat.messages.create\",\n];\n\n// Conservative default: enough for `gh` CLI repo/user/org operations, but\n// without `workflow` (can dispatch CI), `write:packages` (can publish\n// packages), or `project`. Operators who need those can opt in via\n// MOM_GITHUB_OAUTH_SCOPES to keep the blast radius of a compromised agent\n// host explicit and configurable.\nconst DEFAULT_GITHUB_OAUTH_SCOPES = [\"repo\", \"read:user\", \"user:email\", \"read:org\", \"gist\"];\n\nfunction resolveScopesFromEnv(envKey: string, fallback: string[]): string[] {\n const raw = process.env[envKey]?.trim();\n if (!raw) return fallback;\n\n const scopes = raw\n .split(/[\\s,]+/)\n .map((scope) => scope.trim())\n .filter(Boolean);\n\n return scopes.length > 0 ? scopes : fallback;\n}\n\nfunction resolveGoogleWorkspaceCliScopes(): string[] {\n return resolveScopesFromEnv(\n \"MOM_GOOGLE_WORKSPACE_CLI_OAUTH_SCOPES\",\n DEFAULT_GOOGLE_WORKSPACE_CLI_SCOPES,\n );\n}\n\nfunction resolveGitHubOAuthScopes(): string[] {\n return resolveScopesFromEnv(\"MOM_GITHUB_OAUTH_SCOPES\", DEFAULT_GITHUB_OAUTH_SCOPES);\n}\n\nfunction getBuiltinOAuthServices(): OAuthService[] {\n return [\n {\n id: \"github\",\n label: \"GitHub\",\n aliases: [\"github\", \"github_oauth\", \"gh_oauth\"],\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n tokenUrl: \"https://github.com/login/oauth/access_token\",\n scopes: resolveGitHubOAuthScopes(),\n clientIdEnvKey: \"GITHUB_OAUTH_CLIENT_ID\",\n clientSecretEnvKey: \"GITHUB_OAUTH_CLIENT_SECRET\",\n accessTokenEnvKey: \"GITHUB_OAUTH_ACCESS_TOKEN\",\n additionalAccessTokenEnvKeys: [\"GH_TOKEN\"],\n refreshTokenEnvKey: \"GITHUB_OAUTH_REFRESH_TOKEN\",\n },\n {\n id: \"google_workspace_cli\",\n label: \"Google Workspace CLI\",\n aliases: [\"google_workspace_cli\", \"gws\", \"googleworkspace\", \"google-workspace-cli\"],\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n scopes: resolveGoogleWorkspaceCliScopes(),\n clientIdEnvKey: \"GOOGLE_WORKSPACE_CLI_CLIENT_ID\",\n clientSecretEnvKey: \"GOOGLE_WORKSPACE_CLI_CLIENT_SECRET\",\n authorizationParams: {\n access_type: \"offline\",\n include_granted_scopes: \"true\",\n prompt: \"consent\",\n },\n fileOutput: {\n type: \"authorized_user\",\n relativePath: \"gws.json\",\n targetPath: \"/root/.config/gws/credentials.json\",\n },\n },\n ];\n}\n\nexport function getOAuthServices(): OAuthService[] {\n const raw = process.env.MOM_OAUTH_SERVICES_JSON?.trim();\n const builtins = getBuiltinOAuthServices();\n if (!raw) return builtins;\n\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return builtins;\n\n const custom = parsed\n .map((entry): OAuthService | null => {\n if (!entry || typeof entry !== \"object\") return null;\n const obj = entry as Record<string, unknown>;\n const id = typeof obj.id === \"string\" ? obj.id.trim() : \"\";\n const label = typeof obj.label === \"string\" ? obj.label.trim() : \"\";\n const authorizationUrl =\n typeof obj.authorizationUrl === \"string\" ? obj.authorizationUrl.trim() : \"\";\n const tokenUrl = typeof obj.tokenUrl === \"string\" ? obj.tokenUrl.trim() : \"\";\n const clientIdEnvKey =\n typeof obj.clientIdEnvKey === \"string\" ? obj.clientIdEnvKey.trim() : \"\";\n const clientSecretEnvKey =\n typeof obj.clientSecretEnvKey === \"string\" ? obj.clientSecretEnvKey.trim() : \"\";\n const accessTokenEnvKey =\n typeof obj.accessTokenEnvKey === \"string\" ? obj.accessTokenEnvKey.trim() : undefined;\n if (\n !id ||\n !label ||\n !authorizationUrl ||\n !tokenUrl ||\n !clientIdEnvKey ||\n !clientSecretEnvKey\n ) {\n return null;\n }\n\n let fileOutput: OAuthService[\"fileOutput\"];\n if (obj.fileOutput && typeof obj.fileOutput === \"object\") {\n const fileOutputObj = obj.fileOutput as Record<string, unknown>;\n const type = typeof fileOutputObj.type === \"string\" ? fileOutputObj.type.trim() : \"\";\n const relativePath =\n typeof fileOutputObj.relativePath === \"string\" ? fileOutputObj.relativePath.trim() : \"\";\n const targetPath =\n typeof fileOutputObj.targetPath === \"string\"\n ? fileOutputObj.targetPath.trim()\n : undefined;\n const envKey =\n typeof fileOutputObj.envKey === \"string\" ? fileOutputObj.envKey.trim() : undefined;\n if (type === \"authorized_user\" && relativePath) {\n fileOutput = { type: \"authorized_user\", relativePath, targetPath, envKey };\n }\n }\n\n return {\n id: id.toLowerCase(),\n label,\n aliases: Array.isArray(obj.aliases)\n ? obj.aliases\n .filter((v): v is string => typeof v === \"string\")\n .map((v) => v.toLowerCase())\n : [id.toLowerCase()],\n authorizationUrl,\n tokenUrl,\n scopes: Array.isArray(obj.scopes)\n ? obj.scopes.filter((v): v is string => typeof v === \"string\")\n : [],\n clientIdEnvKey,\n clientSecretEnvKey,\n accessTokenEnvKey,\n additionalAccessTokenEnvKeys: Array.isArray(obj.additionalAccessTokenEnvKeys)\n ? obj.additionalAccessTokenEnvKeys.filter((v): v is string => typeof v === \"string\")\n : undefined,\n refreshTokenEnvKey:\n typeof obj.refreshTokenEnvKey === \"string\" ? obj.refreshTokenEnvKey.trim() : undefined,\n authorizationParams:\n obj.authorizationParams && typeof obj.authorizationParams === \"object\"\n ? Object.fromEntries(\n Object.entries(obj.authorizationParams as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n )\n : undefined,\n fileOutput,\n };\n })\n .filter((service): service is OAuthService => service !== null);\n\n const byId = new Map<string, OAuthService>();\n for (const service of builtins) byId.set(service.id, service);\n for (const service of custom) byId.set(service.id, service);\n return [...byId.values()];\n } catch {\n return builtins;\n }\n}\n\nexport function resolveOAuthService(input: string): OAuthService | undefined {\n const normalized = input.trim().toLowerCase();\n if (!normalized) return undefined;\n return getOAuthServices().find(\n (service) => service.id === normalized || service.aliases.includes(normalized),\n );\n}\n\nexport function parseLoginCommand(text: string): ParsedLoginCommand | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const command = tokens[0].toLowerCase();\n if (command !== \"login\" && command !== \"/login\" && command !== \"/pi-login\") {\n return null;\n }\n\n return { command: command as \"login\" | \"/login\" | \"/pi-login\" };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"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","sourcesContent":["export type LoginCredentialKind = \"api_key\" | \"oauth\";\n\nexport interface OAuthAuthorizedUserFileOutput {\n type: \"authorized_user\";\n relativePath: string;\n targetPath?: string;\n envKey?: string;\n}\n\nexport interface OAuthService {\n id: string;\n label: string;\n aliases: string[];\n authorizationUrl: string;\n tokenUrl: string;\n scopes: string[];\n clientIdEnvKey: string;\n clientSecretEnvKey: string;\n accessTokenEnvKey?: string;\n additionalAccessTokenEnvKeys?: string[];\n refreshTokenEnvKey?: string;\n authorizationParams?: Record<string, string>;\n fileOutput?: OAuthAuthorizedUserFileOutput;\n}\n\nexport interface ParsedLoginCommand {\n command: \"login\" | \"/login\" | \"/pi-login\";\n}\n\nconst DEFAULT_GOOGLE_WORKSPACE_CLI_SCOPES = [\n \"https://www.googleapis.com/auth/drive\",\n \"https://mail.google.com/\",\n \"https://www.googleapis.com/auth/calendar\",\n \"https://www.googleapis.com/auth/spreadsheets\",\n \"https://www.googleapis.com/auth/documents\",\n \"https://www.googleapis.com/auth/chat.messages.create\",\n];\n\n// Conservative default: enough for `gh` CLI repo/user/org operations, but\n// without `workflow` (can dispatch CI), `write:packages` (can publish\n// packages), or `project`. Operators who need those can opt in via\n// MOM_GITHUB_OAUTH_SCOPES to keep the blast radius of a compromised agent\n// host explicit and configurable.\nconst DEFAULT_GITHUB_OAUTH_SCOPES = [\"repo\", \"read:user\", \"user:email\", \"read:org\", \"gist\"];\n\nfunction resolveScopesFromEnv(envKey: string, fallback: string[]): string[] {\n const raw = process.env[envKey]?.trim();\n if (!raw) return fallback;\n\n const scopes = raw\n .split(/[\\s,]+/)\n .map((scope) => scope.trim())\n .filter(Boolean);\n\n return scopes.length > 0 ? scopes : fallback;\n}\n\nfunction resolveGoogleWorkspaceCliScopes(): string[] {\n return resolveScopesFromEnv(\n \"MOM_GOOGLE_WORKSPACE_CLI_OAUTH_SCOPES\",\n DEFAULT_GOOGLE_WORKSPACE_CLI_SCOPES,\n );\n}\n\nfunction resolveGitHubOAuthScopes(): string[] {\n return resolveScopesFromEnv(\"MOM_GITHUB_OAUTH_SCOPES\", DEFAULT_GITHUB_OAUTH_SCOPES);\n}\n\nfunction getBuiltinOAuthServices(): OAuthService[] {\n return [\n {\n id: \"github\",\n label: \"GitHub\",\n aliases: [\"github\", \"github_oauth\", \"gh_oauth\"],\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n tokenUrl: \"https://github.com/login/oauth/access_token\",\n scopes: resolveGitHubOAuthScopes(),\n clientIdEnvKey: \"GITHUB_OAUTH_CLIENT_ID\",\n clientSecretEnvKey: \"GITHUB_OAUTH_CLIENT_SECRET\",\n accessTokenEnvKey: \"GITHUB_OAUTH_ACCESS_TOKEN\",\n additionalAccessTokenEnvKeys: [\"GH_TOKEN\"],\n refreshTokenEnvKey: \"GITHUB_OAUTH_REFRESH_TOKEN\",\n },\n {\n id: \"google_workspace_cli\",\n label: \"Google Workspace CLI\",\n aliases: [\"google_workspace_cli\", \"gws\", \"googleworkspace\", \"google-workspace-cli\"],\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n scopes: resolveGoogleWorkspaceCliScopes(),\n clientIdEnvKey: \"GOOGLE_WORKSPACE_CLI_CLIENT_ID\",\n clientSecretEnvKey: \"GOOGLE_WORKSPACE_CLI_CLIENT_SECRET\",\n authorizationParams: {\n access_type: \"offline\",\n include_granted_scopes: \"true\",\n prompt: \"consent\",\n },\n fileOutput: {\n type: \"authorized_user\",\n relativePath: \"gws.json\",\n targetPath: \"/root/.config/gws/credentials.json\",\n },\n },\n ];\n}\n\nexport function getOAuthServices(): OAuthService[] {\n const raw = process.env.MOM_OAUTH_SERVICES_JSON?.trim();\n const builtins = getBuiltinOAuthServices();\n if (!raw) return builtins;\n\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return builtins;\n\n const custom = parsed\n .map((entry): OAuthService | null => {\n if (!entry || typeof entry !== \"object\") return null;\n const obj = entry as Record<string, unknown>;\n const id = typeof obj.id === \"string\" ? obj.id.trim() : \"\";\n const label = typeof obj.label === \"string\" ? obj.label.trim() : \"\";\n const authorizationUrl =\n typeof obj.authorizationUrl === \"string\" ? obj.authorizationUrl.trim() : \"\";\n const tokenUrl = typeof obj.tokenUrl === \"string\" ? obj.tokenUrl.trim() : \"\";\n const clientIdEnvKey =\n typeof obj.clientIdEnvKey === \"string\" ? obj.clientIdEnvKey.trim() : \"\";\n const clientSecretEnvKey =\n typeof obj.clientSecretEnvKey === \"string\" ? obj.clientSecretEnvKey.trim() : \"\";\n const accessTokenEnvKey =\n typeof obj.accessTokenEnvKey === \"string\" ? obj.accessTokenEnvKey.trim() : undefined;\n if (\n !id ||\n !label ||\n !authorizationUrl ||\n !tokenUrl ||\n !clientIdEnvKey ||\n !clientSecretEnvKey\n ) {\n return null;\n }\n\n let fileOutput: OAuthService[\"fileOutput\"];\n if (obj.fileOutput && typeof obj.fileOutput === \"object\") {\n const fileOutputObj = obj.fileOutput as Record<string, unknown>;\n const type = typeof fileOutputObj.type === \"string\" ? fileOutputObj.type.trim() : \"\";\n const relativePath =\n typeof fileOutputObj.relativePath === \"string\" ? fileOutputObj.relativePath.trim() : \"\";\n const targetPath =\n typeof fileOutputObj.targetPath === \"string\"\n ? fileOutputObj.targetPath.trim()\n : undefined;\n const envKey =\n typeof fileOutputObj.envKey === \"string\" ? fileOutputObj.envKey.trim() : undefined;\n if (type === \"authorized_user\" && relativePath) {\n fileOutput = { type: \"authorized_user\", relativePath, targetPath, envKey };\n }\n }\n\n return {\n id: id.toLowerCase(),\n label,\n aliases: Array.isArray(obj.aliases)\n ? obj.aliases\n .filter((v): v is string => typeof v === \"string\")\n .map((v) => v.toLowerCase())\n : [id.toLowerCase()],\n authorizationUrl,\n tokenUrl,\n scopes: Array.isArray(obj.scopes)\n ? obj.scopes.filter((v): v is string => typeof v === \"string\")\n : [],\n clientIdEnvKey,\n clientSecretEnvKey,\n accessTokenEnvKey,\n additionalAccessTokenEnvKeys: Array.isArray(obj.additionalAccessTokenEnvKeys)\n ? obj.additionalAccessTokenEnvKeys.filter((v): v is string => typeof v === \"string\")\n : undefined,\n refreshTokenEnvKey:\n typeof obj.refreshTokenEnvKey === \"string\" ? obj.refreshTokenEnvKey.trim() : undefined,\n authorizationParams:\n obj.authorizationParams && typeof obj.authorizationParams === \"object\"\n ? Object.fromEntries(\n Object.entries(obj.authorizationParams as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n )\n : undefined,\n fileOutput,\n };\n })\n .filter((service): service is OAuthService => service !== null);\n\n const byId = new Map<string, OAuthService>();\n for (const service of builtins) byId.set(service.id, service);\n for (const service of custom) byId.set(service.id, service);\n return [...byId.values()];\n } catch {\n return builtins;\n }\n}\n\nexport function resolveOAuthService(input: string): OAuthService | undefined {\n const normalized = input.trim().toLowerCase();\n if (!normalized) return undefined;\n return getOAuthServices().find(\n (service) => service.id === normalized || service.aliases.includes(normalized),\n );\n}\n\nexport function parseLoginCommand(text: string): ParsedLoginCommand | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const command = tokens[0].toLowerCase();\n if (command !== \"login\" && command !== \"/login\" && command !== \"/pi-login\") {\n return null;\n }\n\n return { command: command as \"login\" | \"/login\" | \"/pi-login\" };\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Server } from "http";
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
2
|
+
import type { InMemorySessionViewTokenStore } from "../session-view/store.js";
|
|
3
|
+
import type { InMemoryLinkTokenStore } from "./session.js";
|
|
4
|
+
import { type VaultManager } from "../vault.js";
|
|
4
5
|
/** Called after a binding is written, to notify the user in chat */
|
|
5
6
|
export type NotifyFn = (platform: string, conversationId: string, message: string) => Promise<void>;
|
|
6
7
|
/**
|
|
@@ -13,5 +14,5 @@ export type NotifyFn = (platform: string, conversationId: string, message: strin
|
|
|
13
14
|
* POST /api/oauth/start — creates provider OAuth redirect URL
|
|
14
15
|
* GET /oauth/callback — OAuth callback endpoint
|
|
15
16
|
*/
|
|
16
|
-
export declare function startLinkServer(port: number, linkTokenStore: InMemoryLinkTokenStore, vaultManager: VaultManager, notify: NotifyFn): Server;
|
|
17
|
-
//# sourceMappingURL=
|
|
17
|
+
export declare function startLinkServer(port: number, linkTokenStore: InMemoryLinkTokenStore, vaultManager: VaultManager, notify: NotifyFn, sessionViewTokenStore?: InMemorySessionViewTokenStore): Server;
|
|
18
|
+
//# sourceMappingURL=portal.d.ts.map
|