surface-cli 0.1.1 → 0.3.0
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 +42 -15
- package/dist/cli.js +236 -21
- package/dist/cli.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/contracts/account.js +8 -0
- package/dist/contracts/account.js.map +1 -1
- package/dist/e2e/gmail-v1.js +31 -0
- package/dist/e2e/gmail-v1.js.map +1 -1
- package/dist/e2e/outlook-v1.js +11 -0
- package/dist/e2e/outlook-v1.js.map +1 -1
- package/dist/lib/remote-auth.js +18 -2
- package/dist/lib/remote-auth.js.map +1 -1
- package/dist/lib/stored-mail.js +69 -0
- package/dist/lib/stored-mail.js.map +1 -0
- package/dist/paths.js +2 -0
- package/dist/paths.js.map +1 -1
- package/dist/providers/gmail/adapter.js +336 -53
- package/dist/providers/gmail/adapter.js.map +1 -1
- package/dist/providers/gmail/api.js +68 -0
- package/dist/providers/gmail/api.js.map +1 -1
- package/dist/providers/gmail/normalize.js.map +1 -1
- package/dist/providers/gmail/oauth.js +4 -0
- package/dist/providers/gmail/oauth.js.map +1 -1
- package/dist/providers/outlook/adapter.js +185 -97
- package/dist/providers/outlook/adapter.js.map +1 -1
- package/dist/providers/outlook/extract.js +7 -0
- package/dist/providers/outlook/extract.js.map +1 -1
- package/dist/providers/shared/inline-attachments.js +17 -0
- package/dist/providers/shared/inline-attachments.js.map +1 -0
- package/dist/refs.js +3 -0
- package/dist/refs.js.map +1 -1
- package/dist/session-daemon.js +218 -0
- package/dist/session-daemon.js.map +1 -0
- package/dist/session.js +283 -0
- package/dist/session.js.map +1 -0
- package/dist/state/database.js +542 -8
- package/dist/state/database.js.map +1 -1
- package/dist/summarizer.js +259 -76
- package/dist/summarizer.js.map +1 -1
- package/package.json +1 -1
package/dist/state/database.js
CHANGED
|
@@ -24,6 +24,19 @@ export class SurfaceDatabase {
|
|
|
24
24
|
updated_at TEXT NOT NULL
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
+
CREATE TABLE IF NOT EXISTS account_identities (
|
|
28
|
+
account_id TEXT PRIMARY KEY,
|
|
29
|
+
primary_email TEXT NOT NULL,
|
|
30
|
+
display_name TEXT,
|
|
31
|
+
email_aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
32
|
+
name_aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
33
|
+
primary_email_source TEXT NOT NULL DEFAULT 'configured',
|
|
34
|
+
display_name_source TEXT,
|
|
35
|
+
verified_at TEXT,
|
|
36
|
+
updated_at TEXT NOT NULL,
|
|
37
|
+
FOREIGN KEY(account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
|
|
38
|
+
);
|
|
39
|
+
|
|
27
40
|
CREATE TABLE IF NOT EXISTS threads (
|
|
28
41
|
thread_ref TEXT PRIMARY KEY,
|
|
29
42
|
account_id TEXT NOT NULL,
|
|
@@ -101,13 +114,34 @@ export class SurfaceDatabase {
|
|
|
101
114
|
brief TEXT NOT NULL,
|
|
102
115
|
needs_action INTEGER NOT NULL DEFAULT 0,
|
|
103
116
|
importance TEXT NOT NULL,
|
|
117
|
+
fingerprint TEXT,
|
|
104
118
|
generated_at TEXT NOT NULL,
|
|
105
119
|
FOREIGN KEY(thread_ref) REFERENCES threads(thread_ref) ON DELETE CASCADE
|
|
106
120
|
);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
123
|
+
session_id TEXT PRIMARY KEY,
|
|
124
|
+
account_id TEXT NOT NULL,
|
|
125
|
+
provider TEXT NOT NULL,
|
|
126
|
+
transport TEXT NOT NULL,
|
|
127
|
+
socket_path TEXT NOT NULL,
|
|
128
|
+
auth_token TEXT NOT NULL,
|
|
129
|
+
status TEXT NOT NULL,
|
|
130
|
+
pid INTEGER,
|
|
131
|
+
idle_timeout_seconds INTEGER NOT NULL,
|
|
132
|
+
max_age_seconds INTEGER NOT NULL,
|
|
133
|
+
error_detail TEXT,
|
|
134
|
+
created_at TEXT NOT NULL,
|
|
135
|
+
last_used_at TEXT NOT NULL,
|
|
136
|
+
closed_at TEXT,
|
|
137
|
+
FOREIGN KEY(account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
|
|
138
|
+
);
|
|
107
139
|
`);
|
|
108
140
|
this.ensureColumn("threads", "participants_json", "TEXT NOT NULL DEFAULT '[]'");
|
|
109
141
|
this.ensureColumn("messages", "invite_json", "TEXT");
|
|
142
|
+
this.ensureColumn("summaries", "fingerprint", "TEXT");
|
|
110
143
|
this.ensureProviderLocatorSchema();
|
|
144
|
+
this.seedMissingAccountIdentities();
|
|
111
145
|
}
|
|
112
146
|
tableColumns(tableName) {
|
|
113
147
|
return this.connection
|
|
@@ -158,6 +192,153 @@ export class SurfaceDatabase {
|
|
|
158
192
|
}
|
|
159
193
|
this.connection.exec("DROP TABLE IF EXISTS provider_locators_legacy;");
|
|
160
194
|
}
|
|
195
|
+
parseStringArray(rawValue) {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(rawValue);
|
|
198
|
+
return Array.isArray(parsed)
|
|
199
|
+
? parsed.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
200
|
+
: [];
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
normalizeEmailList(values) {
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
const normalized = [];
|
|
209
|
+
for (const value of values) {
|
|
210
|
+
const candidate = value.trim().toLowerCase();
|
|
211
|
+
if (!candidate || seen.has(candidate)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
seen.add(candidate);
|
|
215
|
+
normalized.push(candidate);
|
|
216
|
+
}
|
|
217
|
+
return normalized;
|
|
218
|
+
}
|
|
219
|
+
isPlaceholderEmail(value) {
|
|
220
|
+
return value.trim().toLowerCase().endsWith("@placeholder.local");
|
|
221
|
+
}
|
|
222
|
+
normalizeNameList(values) {
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
const normalized = [];
|
|
225
|
+
for (const value of values) {
|
|
226
|
+
const candidate = value.trim().replace(/\s+/g, " ");
|
|
227
|
+
const key = candidate.toLowerCase();
|
|
228
|
+
if (!candidate || seen.has(key)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
seen.add(key);
|
|
232
|
+
normalized.push(candidate);
|
|
233
|
+
}
|
|
234
|
+
return normalized;
|
|
235
|
+
}
|
|
236
|
+
rowToAccountIdentity(row) {
|
|
237
|
+
return {
|
|
238
|
+
account_id: row.account_id,
|
|
239
|
+
primary_email: row.primary_email,
|
|
240
|
+
display_name: row.display_name,
|
|
241
|
+
email_aliases: this.parseStringArray(row.email_aliases_json),
|
|
242
|
+
name_aliases: this.parseStringArray(row.name_aliases_json),
|
|
243
|
+
primary_email_source: row.primary_email_source,
|
|
244
|
+
display_name_source: row.display_name_source,
|
|
245
|
+
verified_at: row.verified_at,
|
|
246
|
+
updated_at: row.updated_at,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
seedMissingAccountIdentities() {
|
|
250
|
+
const timestamp = nowIsoUtc();
|
|
251
|
+
this.connection
|
|
252
|
+
.prepare(`
|
|
253
|
+
INSERT OR IGNORE INTO account_identities (
|
|
254
|
+
account_id,
|
|
255
|
+
primary_email,
|
|
256
|
+
email_aliases_json,
|
|
257
|
+
name_aliases_json,
|
|
258
|
+
primary_email_source,
|
|
259
|
+
display_name_source,
|
|
260
|
+
verified_at,
|
|
261
|
+
updated_at
|
|
262
|
+
)
|
|
263
|
+
SELECT
|
|
264
|
+
account_id,
|
|
265
|
+
lower(email),
|
|
266
|
+
'[]',
|
|
267
|
+
'[]',
|
|
268
|
+
'configured',
|
|
269
|
+
NULL,
|
|
270
|
+
NULL,
|
|
271
|
+
@updated_at
|
|
272
|
+
FROM accounts
|
|
273
|
+
`)
|
|
274
|
+
.run({ updated_at: timestamp });
|
|
275
|
+
}
|
|
276
|
+
seedOrUpdateConfiguredIdentity(account) {
|
|
277
|
+
const existing = this.findAccountIdentity(account);
|
|
278
|
+
const timestamp = nowIsoUtc();
|
|
279
|
+
if (!existing) {
|
|
280
|
+
this.connection
|
|
281
|
+
.prepare(`
|
|
282
|
+
INSERT INTO account_identities (
|
|
283
|
+
account_id,
|
|
284
|
+
primary_email,
|
|
285
|
+
email_aliases_json,
|
|
286
|
+
name_aliases_json,
|
|
287
|
+
primary_email_source,
|
|
288
|
+
display_name_source,
|
|
289
|
+
verified_at,
|
|
290
|
+
updated_at
|
|
291
|
+
) VALUES (
|
|
292
|
+
@account_id,
|
|
293
|
+
@primary_email,
|
|
294
|
+
'[]',
|
|
295
|
+
'[]',
|
|
296
|
+
'configured',
|
|
297
|
+
NULL,
|
|
298
|
+
NULL,
|
|
299
|
+
@updated_at
|
|
300
|
+
)
|
|
301
|
+
`)
|
|
302
|
+
.run({
|
|
303
|
+
account_id: account.account_id,
|
|
304
|
+
primary_email: account.email.trim().toLowerCase(),
|
|
305
|
+
updated_at: timestamp,
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (existing.primary_email_source !== "configured") {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const primaryEmail = account.email.trim().toLowerCase();
|
|
313
|
+
if (existing.primary_email === primaryEmail) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this.connection
|
|
317
|
+
.prepare(`
|
|
318
|
+
UPDATE account_identities
|
|
319
|
+
SET primary_email = @primary_email,
|
|
320
|
+
updated_at = @updated_at
|
|
321
|
+
WHERE account_id = @account_id
|
|
322
|
+
`)
|
|
323
|
+
.run({
|
|
324
|
+
account_id: account.account_id,
|
|
325
|
+
primary_email: primaryEmail,
|
|
326
|
+
updated_at: timestamp,
|
|
327
|
+
});
|
|
328
|
+
this.clearSummariesForAccount(account.account_id);
|
|
329
|
+
}
|
|
330
|
+
clearSummariesForAccount(accountId) {
|
|
331
|
+
this.connection
|
|
332
|
+
.prepare(`
|
|
333
|
+
DELETE FROM summaries
|
|
334
|
+
WHERE thread_ref IN (
|
|
335
|
+
SELECT thread_ref
|
|
336
|
+
FROM threads
|
|
337
|
+
WHERE account_id = ?
|
|
338
|
+
)
|
|
339
|
+
`)
|
|
340
|
+
.run(accountId);
|
|
341
|
+
}
|
|
161
342
|
upsertAccount(input) {
|
|
162
343
|
const existing = this.findAccountByName(input.name);
|
|
163
344
|
const timestamp = nowIsoUtc();
|
|
@@ -178,7 +359,9 @@ export class SurfaceDatabase {
|
|
|
178
359
|
email: input.email,
|
|
179
360
|
updated_at: timestamp,
|
|
180
361
|
});
|
|
181
|
-
|
|
362
|
+
const account = this.findAccountByName(input.name);
|
|
363
|
+
this.seedOrUpdateConfiguredIdentity(account);
|
|
364
|
+
return account;
|
|
182
365
|
}
|
|
183
366
|
const account = {
|
|
184
367
|
account_id: makeAccountId(),
|
|
@@ -210,8 +393,144 @@ export class SurfaceDatabase {
|
|
|
210
393
|
)
|
|
211
394
|
`)
|
|
212
395
|
.run(account);
|
|
396
|
+
this.seedOrUpdateConfiguredIdentity(account);
|
|
213
397
|
return account;
|
|
214
398
|
}
|
|
399
|
+
findAccountIdentity(account) {
|
|
400
|
+
const row = this.connection
|
|
401
|
+
.prepare(`
|
|
402
|
+
SELECT
|
|
403
|
+
account_id,
|
|
404
|
+
primary_email,
|
|
405
|
+
display_name,
|
|
406
|
+
email_aliases_json,
|
|
407
|
+
name_aliases_json,
|
|
408
|
+
primary_email_source,
|
|
409
|
+
display_name_source,
|
|
410
|
+
verified_at,
|
|
411
|
+
updated_at
|
|
412
|
+
FROM account_identities
|
|
413
|
+
WHERE account_id = ?
|
|
414
|
+
LIMIT 1
|
|
415
|
+
`)
|
|
416
|
+
.get(account.account_id);
|
|
417
|
+
return row ? this.rowToAccountIdentity(row) : undefined;
|
|
418
|
+
}
|
|
419
|
+
getAccountIdentity(account) {
|
|
420
|
+
const existing = this.findAccountIdentity(account);
|
|
421
|
+
if (existing) {
|
|
422
|
+
return existing;
|
|
423
|
+
}
|
|
424
|
+
this.seedOrUpdateConfiguredIdentity(account);
|
|
425
|
+
const seeded = this.findAccountIdentity(account);
|
|
426
|
+
if (!seeded) {
|
|
427
|
+
throw new Error(`Account identity could not be created for account '${account.name}'.`);
|
|
428
|
+
}
|
|
429
|
+
return seeded;
|
|
430
|
+
}
|
|
431
|
+
updateAccountIdentityFromUser(account, input) {
|
|
432
|
+
const existing = this.getAccountIdentity(account);
|
|
433
|
+
const timestamp = nowIsoUtc();
|
|
434
|
+
const primaryEmail = input.primary_email?.trim().toLowerCase() ?? existing.primary_email;
|
|
435
|
+
const displayName = input.display_name?.trim().replace(/\s+/g, " ") ?? existing.display_name;
|
|
436
|
+
const emailAliases = this.normalizeEmailList([
|
|
437
|
+
...(input.clear_email_aliases ? [] : existing.email_aliases),
|
|
438
|
+
...(input.email_aliases ?? []),
|
|
439
|
+
].filter((email) => email.trim().toLowerCase() !== primaryEmail));
|
|
440
|
+
const nameAliases = this.normalizeNameList([
|
|
441
|
+
...(input.clear_name_aliases ? [] : existing.name_aliases),
|
|
442
|
+
...(input.name_aliases ?? []),
|
|
443
|
+
].filter((name) => name.trim().replace(/\s+/g, " ") !== displayName));
|
|
444
|
+
this.connection
|
|
445
|
+
.prepare(`
|
|
446
|
+
UPDATE account_identities
|
|
447
|
+
SET primary_email = @primary_email,
|
|
448
|
+
display_name = @display_name,
|
|
449
|
+
email_aliases_json = @email_aliases_json,
|
|
450
|
+
name_aliases_json = @name_aliases_json,
|
|
451
|
+
primary_email_source = @primary_email_source,
|
|
452
|
+
display_name_source = @display_name_source,
|
|
453
|
+
verified_at = @verified_at,
|
|
454
|
+
updated_at = @updated_at
|
|
455
|
+
WHERE account_id = @account_id
|
|
456
|
+
`)
|
|
457
|
+
.run({
|
|
458
|
+
account_id: account.account_id,
|
|
459
|
+
primary_email: primaryEmail,
|
|
460
|
+
display_name: displayName,
|
|
461
|
+
email_aliases_json: JSON.stringify(emailAliases),
|
|
462
|
+
name_aliases_json: JSON.stringify(nameAliases),
|
|
463
|
+
primary_email_source: input.primary_email ? "user_confirmed" : existing.primary_email_source,
|
|
464
|
+
display_name_source: input.display_name ? "user_confirmed" : existing.display_name_source,
|
|
465
|
+
verified_at: input.primary_email ? timestamp : existing.verified_at,
|
|
466
|
+
updated_at: timestamp,
|
|
467
|
+
});
|
|
468
|
+
if (input.primary_email) {
|
|
469
|
+
this.connection
|
|
470
|
+
.prepare(`
|
|
471
|
+
UPDATE accounts
|
|
472
|
+
SET email = @email,
|
|
473
|
+
updated_at = @updated_at
|
|
474
|
+
WHERE account_id = @account_id
|
|
475
|
+
`)
|
|
476
|
+
.run({
|
|
477
|
+
account_id: account.account_id,
|
|
478
|
+
email: primaryEmail,
|
|
479
|
+
updated_at: timestamp,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
this.clearSummariesForAccount(account.account_id);
|
|
483
|
+
return this.getAccountIdentity(account);
|
|
484
|
+
}
|
|
485
|
+
updateAccountIdentityFromProvider(account, authenticatedEmail) {
|
|
486
|
+
const existing = this.getAccountIdentity(account);
|
|
487
|
+
const timestamp = nowIsoUtc();
|
|
488
|
+
const primaryEmail = authenticatedEmail.trim().toLowerCase();
|
|
489
|
+
const emailAliases = this.normalizeEmailList([
|
|
490
|
+
...existing.email_aliases,
|
|
491
|
+
...(existing.primary_email
|
|
492
|
+
&& existing.primary_email !== primaryEmail
|
|
493
|
+
&& !this.isPlaceholderEmail(existing.primary_email)
|
|
494
|
+
? [existing.primary_email]
|
|
495
|
+
: []),
|
|
496
|
+
].filter((email) => email.trim().toLowerCase() !== primaryEmail));
|
|
497
|
+
const identityChanged = primaryEmail !== existing.primary_email
|
|
498
|
+
|| existing.primary_email_source !== "provider_verified"
|
|
499
|
+
|| JSON.stringify(emailAliases) !== JSON.stringify(existing.email_aliases);
|
|
500
|
+
this.connection
|
|
501
|
+
.prepare(`
|
|
502
|
+
UPDATE account_identities
|
|
503
|
+
SET primary_email = @primary_email,
|
|
504
|
+
email_aliases_json = @email_aliases_json,
|
|
505
|
+
primary_email_source = 'provider_verified',
|
|
506
|
+
verified_at = @verified_at,
|
|
507
|
+
updated_at = @updated_at
|
|
508
|
+
WHERE account_id = @account_id
|
|
509
|
+
`)
|
|
510
|
+
.run({
|
|
511
|
+
account_id: account.account_id,
|
|
512
|
+
primary_email: primaryEmail,
|
|
513
|
+
email_aliases_json: JSON.stringify(emailAliases),
|
|
514
|
+
verified_at: timestamp,
|
|
515
|
+
updated_at: timestamp,
|
|
516
|
+
});
|
|
517
|
+
this.connection
|
|
518
|
+
.prepare(`
|
|
519
|
+
UPDATE accounts
|
|
520
|
+
SET email = @email,
|
|
521
|
+
updated_at = @updated_at
|
|
522
|
+
WHERE account_id = @account_id
|
|
523
|
+
`)
|
|
524
|
+
.run({
|
|
525
|
+
account_id: account.account_id,
|
|
526
|
+
email: primaryEmail,
|
|
527
|
+
updated_at: timestamp,
|
|
528
|
+
});
|
|
529
|
+
if (identityChanged) {
|
|
530
|
+
this.clearSummariesForAccount(account.account_id);
|
|
531
|
+
}
|
|
532
|
+
return this.getAccountIdentity({ ...account, email: primaryEmail });
|
|
533
|
+
}
|
|
215
534
|
listAccounts() {
|
|
216
535
|
return this.connection
|
|
217
536
|
.prepare(`
|
|
@@ -509,7 +828,7 @@ export class SurfaceDatabase {
|
|
|
509
828
|
`)
|
|
510
829
|
.run(savedTo, attachmentId);
|
|
511
830
|
}
|
|
512
|
-
upsertSummary(threadRef, summary) {
|
|
831
|
+
upsertSummary(threadRef, summary, fingerprint) {
|
|
513
832
|
this.connection
|
|
514
833
|
.prepare(`
|
|
515
834
|
INSERT INTO summaries (
|
|
@@ -519,6 +838,7 @@ export class SurfaceDatabase {
|
|
|
519
838
|
brief,
|
|
520
839
|
needs_action,
|
|
521
840
|
importance,
|
|
841
|
+
fingerprint,
|
|
522
842
|
generated_at
|
|
523
843
|
) VALUES (
|
|
524
844
|
@thread_ref,
|
|
@@ -527,6 +847,7 @@ export class SurfaceDatabase {
|
|
|
527
847
|
@brief,
|
|
528
848
|
@needs_action,
|
|
529
849
|
@importance,
|
|
850
|
+
@fingerprint,
|
|
530
851
|
@generated_at
|
|
531
852
|
)
|
|
532
853
|
ON CONFLICT(thread_ref) DO UPDATE SET
|
|
@@ -535,6 +856,7 @@ export class SurfaceDatabase {
|
|
|
535
856
|
brief = excluded.brief,
|
|
536
857
|
needs_action = excluded.needs_action,
|
|
537
858
|
importance = excluded.importance,
|
|
859
|
+
fingerprint = excluded.fingerprint,
|
|
538
860
|
generated_at = excluded.generated_at
|
|
539
861
|
`)
|
|
540
862
|
.run({
|
|
@@ -544,13 +866,22 @@ export class SurfaceDatabase {
|
|
|
544
866
|
brief: summary.brief,
|
|
545
867
|
needs_action: summary.needs_action ? 1 : 0,
|
|
546
868
|
importance: summary.importance,
|
|
869
|
+
fingerprint,
|
|
547
870
|
generated_at: nowIsoUtc(),
|
|
548
871
|
});
|
|
549
872
|
}
|
|
550
|
-
|
|
873
|
+
clearSummary(threadRef) {
|
|
874
|
+
this.connection
|
|
875
|
+
.prepare(`
|
|
876
|
+
DELETE FROM summaries
|
|
877
|
+
WHERE thread_ref = ?
|
|
878
|
+
`)
|
|
879
|
+
.run(threadRef);
|
|
880
|
+
}
|
|
881
|
+
findStoredSummary(threadRef) {
|
|
551
882
|
const row = this.connection
|
|
552
883
|
.prepare(`
|
|
553
|
-
SELECT backend, model, brief, needs_action, importance
|
|
884
|
+
SELECT thread_ref, backend, model, brief, needs_action, importance, fingerprint
|
|
554
885
|
FROM summaries
|
|
555
886
|
WHERE thread_ref = ?
|
|
556
887
|
LIMIT 1
|
|
@@ -559,6 +890,151 @@ export class SurfaceDatabase {
|
|
|
559
890
|
if (!row) {
|
|
560
891
|
return null;
|
|
561
892
|
}
|
|
893
|
+
return row;
|
|
894
|
+
}
|
|
895
|
+
createSession(input) {
|
|
896
|
+
const timestamp = nowIsoUtc();
|
|
897
|
+
this.connection
|
|
898
|
+
.prepare(`
|
|
899
|
+
INSERT INTO sessions (
|
|
900
|
+
session_id,
|
|
901
|
+
account_id,
|
|
902
|
+
provider,
|
|
903
|
+
transport,
|
|
904
|
+
socket_path,
|
|
905
|
+
auth_token,
|
|
906
|
+
status,
|
|
907
|
+
pid,
|
|
908
|
+
idle_timeout_seconds,
|
|
909
|
+
max_age_seconds,
|
|
910
|
+
error_detail,
|
|
911
|
+
created_at,
|
|
912
|
+
last_used_at,
|
|
913
|
+
closed_at
|
|
914
|
+
) VALUES (
|
|
915
|
+
@session_id,
|
|
916
|
+
@account_id,
|
|
917
|
+
@provider,
|
|
918
|
+
@transport,
|
|
919
|
+
@socket_path,
|
|
920
|
+
@auth_token,
|
|
921
|
+
'starting',
|
|
922
|
+
NULL,
|
|
923
|
+
@idle_timeout_seconds,
|
|
924
|
+
@max_age_seconds,
|
|
925
|
+
NULL,
|
|
926
|
+
@created_at,
|
|
927
|
+
@last_used_at,
|
|
928
|
+
NULL
|
|
929
|
+
)
|
|
930
|
+
`)
|
|
931
|
+
.run({
|
|
932
|
+
...input,
|
|
933
|
+
created_at: timestamp,
|
|
934
|
+
last_used_at: timestamp,
|
|
935
|
+
});
|
|
936
|
+
return this.getSession(input.session_id);
|
|
937
|
+
}
|
|
938
|
+
getSession(sessionId) {
|
|
939
|
+
return this.connection
|
|
940
|
+
.prepare(`
|
|
941
|
+
SELECT
|
|
942
|
+
session_id,
|
|
943
|
+
account_id,
|
|
944
|
+
provider,
|
|
945
|
+
transport,
|
|
946
|
+
socket_path,
|
|
947
|
+
auth_token,
|
|
948
|
+
status,
|
|
949
|
+
pid,
|
|
950
|
+
idle_timeout_seconds,
|
|
951
|
+
max_age_seconds,
|
|
952
|
+
error_detail,
|
|
953
|
+
created_at,
|
|
954
|
+
last_used_at,
|
|
955
|
+
closed_at
|
|
956
|
+
FROM sessions
|
|
957
|
+
WHERE session_id = ?
|
|
958
|
+
LIMIT 1
|
|
959
|
+
`)
|
|
960
|
+
.get(sessionId);
|
|
961
|
+
}
|
|
962
|
+
listSessions() {
|
|
963
|
+
return this.connection
|
|
964
|
+
.prepare(`
|
|
965
|
+
SELECT
|
|
966
|
+
session_id,
|
|
967
|
+
account_id,
|
|
968
|
+
provider,
|
|
969
|
+
transport,
|
|
970
|
+
socket_path,
|
|
971
|
+
auth_token,
|
|
972
|
+
status,
|
|
973
|
+
pid,
|
|
974
|
+
idle_timeout_seconds,
|
|
975
|
+
max_age_seconds,
|
|
976
|
+
error_detail,
|
|
977
|
+
created_at,
|
|
978
|
+
last_used_at,
|
|
979
|
+
closed_at
|
|
980
|
+
FROM sessions
|
|
981
|
+
ORDER BY created_at DESC
|
|
982
|
+
`)
|
|
983
|
+
.all();
|
|
984
|
+
}
|
|
985
|
+
markSessionRunning(sessionId, pid) {
|
|
986
|
+
this.connection
|
|
987
|
+
.prepare(`
|
|
988
|
+
UPDATE sessions
|
|
989
|
+
SET status = 'running',
|
|
990
|
+
pid = ?,
|
|
991
|
+
error_detail = NULL,
|
|
992
|
+
closed_at = NULL
|
|
993
|
+
WHERE session_id = ?
|
|
994
|
+
`)
|
|
995
|
+
.run(pid, sessionId);
|
|
996
|
+
}
|
|
997
|
+
updateSessionProcessInfo(sessionId, pid) {
|
|
998
|
+
this.connection
|
|
999
|
+
.prepare(`
|
|
1000
|
+
UPDATE sessions
|
|
1001
|
+
SET pid = ?
|
|
1002
|
+
WHERE session_id = ?
|
|
1003
|
+
`)
|
|
1004
|
+
.run(pid, sessionId);
|
|
1005
|
+
}
|
|
1006
|
+
touchSession(sessionId) {
|
|
1007
|
+
this.connection
|
|
1008
|
+
.prepare(`
|
|
1009
|
+
UPDATE sessions
|
|
1010
|
+
SET last_used_at = ?
|
|
1011
|
+
WHERE session_id = ?
|
|
1012
|
+
`)
|
|
1013
|
+
.run(nowIsoUtc(), sessionId);
|
|
1014
|
+
}
|
|
1015
|
+
markSessionClosed(sessionId, status, options = {}) {
|
|
1016
|
+
this.connection
|
|
1017
|
+
.prepare(`
|
|
1018
|
+
UPDATE sessions
|
|
1019
|
+
SET status = @status,
|
|
1020
|
+
error_detail = @error_detail,
|
|
1021
|
+
pid = @pid,
|
|
1022
|
+
closed_at = @closed_at
|
|
1023
|
+
WHERE session_id = @session_id
|
|
1024
|
+
`)
|
|
1025
|
+
.run({
|
|
1026
|
+
session_id: sessionId,
|
|
1027
|
+
status,
|
|
1028
|
+
error_detail: options.errorDetail ?? null,
|
|
1029
|
+
pid: options.pid ?? null,
|
|
1030
|
+
closed_at: nowIsoUtc(),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
findSummary(threadRef) {
|
|
1034
|
+
const row = this.findStoredSummary(threadRef);
|
|
1035
|
+
if (!row) {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
562
1038
|
return {
|
|
563
1039
|
backend: row.backend,
|
|
564
1040
|
model: row.model,
|
|
@@ -604,7 +1080,30 @@ export class SurfaceDatabase {
|
|
|
604
1080
|
`)
|
|
605
1081
|
.get(messageRef);
|
|
606
1082
|
}
|
|
1083
|
+
getStoredThread(threadRef) {
|
|
1084
|
+
return this.connection
|
|
1085
|
+
.prepare(`
|
|
1086
|
+
SELECT
|
|
1087
|
+
thread_ref,
|
|
1088
|
+
account_id,
|
|
1089
|
+
subject,
|
|
1090
|
+
mailbox,
|
|
1091
|
+
participants_json,
|
|
1092
|
+
labels_json,
|
|
1093
|
+
received_at,
|
|
1094
|
+
message_count,
|
|
1095
|
+
unread_count,
|
|
1096
|
+
has_attachments
|
|
1097
|
+
FROM threads
|
|
1098
|
+
WHERE thread_ref = ?
|
|
1099
|
+
LIMIT 1
|
|
1100
|
+
`)
|
|
1101
|
+
.get(threadRef);
|
|
1102
|
+
}
|
|
607
1103
|
updateInviteStatusForThread(threadRef, responseStatus) {
|
|
1104
|
+
this.updateInviteForThread(threadRef, { response_status: responseStatus });
|
|
1105
|
+
}
|
|
1106
|
+
updateInviteForThread(threadRef, patch) {
|
|
608
1107
|
const rows = this.connection
|
|
609
1108
|
.prepare(`
|
|
610
1109
|
SELECT message_ref, invite_json
|
|
@@ -627,10 +1126,7 @@ export class SurfaceDatabase {
|
|
|
627
1126
|
const invite = JSON.parse(row.invite_json);
|
|
628
1127
|
update.run({
|
|
629
1128
|
message_ref: row.message_ref,
|
|
630
|
-
invite_json: JSON.stringify({
|
|
631
|
-
...invite,
|
|
632
|
-
response_status: responseStatus,
|
|
633
|
-
}),
|
|
1129
|
+
invite_json: JSON.stringify({ ...invite, ...patch }),
|
|
634
1130
|
last_synced_at: lastSyncedAt,
|
|
635
1131
|
});
|
|
636
1132
|
}
|
|
@@ -645,6 +1141,34 @@ export class SurfaceDatabase {
|
|
|
645
1141
|
`)
|
|
646
1142
|
.all(threadRef).map((row) => row.message_ref);
|
|
647
1143
|
}
|
|
1144
|
+
listStoredMessagesForThread(threadRef) {
|
|
1145
|
+
return this.connection
|
|
1146
|
+
.prepare(`
|
|
1147
|
+
SELECT
|
|
1148
|
+
m.message_ref,
|
|
1149
|
+
m.account_id,
|
|
1150
|
+
m.thread_ref,
|
|
1151
|
+
m.subject,
|
|
1152
|
+
m.from_name,
|
|
1153
|
+
m.from_email,
|
|
1154
|
+
m.to_json,
|
|
1155
|
+
m.cc_json,
|
|
1156
|
+
m.sent_at,
|
|
1157
|
+
m.received_at,
|
|
1158
|
+
m.unread,
|
|
1159
|
+
m.snippet,
|
|
1160
|
+
m.body_cache_path,
|
|
1161
|
+
m.body_cached,
|
|
1162
|
+
m.body_truncated,
|
|
1163
|
+
m.body_cached_bytes,
|
|
1164
|
+
m.invite_json
|
|
1165
|
+
FROM thread_messages tm
|
|
1166
|
+
INNER JOIN messages m ON m.message_ref = tm.message_ref
|
|
1167
|
+
WHERE tm.thread_ref = ?
|
|
1168
|
+
ORDER BY tm.position ASC
|
|
1169
|
+
`)
|
|
1170
|
+
.all(threadRef);
|
|
1171
|
+
}
|
|
648
1172
|
markThreadArchived(threadRef) {
|
|
649
1173
|
this.connection
|
|
650
1174
|
.prepare(`
|
|
@@ -717,6 +1241,16 @@ export class SurfaceDatabase {
|
|
|
717
1241
|
`)
|
|
718
1242
|
.get(messageRef);
|
|
719
1243
|
}
|
|
1244
|
+
findThreadByRef(threadRef) {
|
|
1245
|
+
return this.connection
|
|
1246
|
+
.prepare(`
|
|
1247
|
+
SELECT thread_ref, account_id
|
|
1248
|
+
FROM threads
|
|
1249
|
+
WHERE thread_ref = ?
|
|
1250
|
+
LIMIT 1
|
|
1251
|
+
`)
|
|
1252
|
+
.get(threadRef);
|
|
1253
|
+
}
|
|
720
1254
|
findAttachmentById(attachmentId) {
|
|
721
1255
|
return this.connection
|
|
722
1256
|
.prepare(`
|