@wirecat/cli-core 0.553.0 → 0.555.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli/messenger/catch-up-answers.d.ts +50 -1
  3. package/dist/cli/messenger/catch-up-answers.js +2 -1
  4. package/dist/cli/messenger/catch-up.d.ts +6 -2
  5. package/dist/cli/messenger/catch-up.js +11 -2
  6. package/dist/cli/messenger/conversation-answers.d.ts +3 -0
  7. package/dist/cli/messenger/inbox.js +1 -1
  8. package/dist/cli/messenger/message-answers.d.ts +3 -0
  9. package/dist/cli/messenger/message-writes.d.ts +6 -0
  10. package/dist/cli/messenger/points.d.ts +9 -4
  11. package/dist/cli/messenger/points.js +10 -4
  12. package/dist/cli/messenger/review.js +1 -1
  13. package/dist/cli/schedule/schedule-command.d.ts +0 -1
  14. package/dist/cli/schedule/schedule-command.js +6 -1
  15. package/dist/domain/models.d.ts +4 -0
  16. package/dist/entities/contact-context.d.ts +6 -0
  17. package/dist/entities/conversation-results.d.ts +6 -0
  18. package/dist/entities/evidence.d.ts +24 -0
  19. package/dist/entities/index.d.ts +263 -3
  20. package/dist/entities/index.js +1 -0
  21. package/dist/entities/listener.d.ts +12 -0
  22. package/dist/entities/message-context.d.ts +9 -0
  23. package/dist/entities/message-write.d.ts +12 -0
  24. package/dist/entities/message.d.ts +6 -0
  25. package/dist/entities/message.js +6 -0
  26. package/dist/entities/store-records.d.ts +3 -0
  27. package/dist/mail/cli/index.d.ts +2 -1
  28. package/dist/mail/cli/index.js +4 -1
  29. package/dist/mail/cli/review.d.ts +24 -0
  30. package/dist/mail/cli/review.js +204 -0
  31. package/dist/mail/email-input.d.ts +2 -0
  32. package/dist/mail/email-input.js +6 -1
  33. package/dist/mail/index.d.ts +2 -0
  34. package/dist/mail/index.js +2 -0
  35. package/dist/mail/owed.d.ts +11 -0
  36. package/dist/mail/owed.js +14 -0
  37. package/dist/mail/retry.js +2 -1
  38. package/dist/mail/review.d.ts +37 -0
  39. package/dist/mail/review.js +118 -0
  40. package/dist/mail/sync.js +5 -1
  41. package/dist/mail/testing/fake.d.ts +2 -0
  42. package/dist/mail/testing/fake.js +1 -1
  43. package/dist/services/agenda-mail.d.ts +15 -0
  44. package/dist/services/agenda-mail.js +66 -0
  45. package/dist/services/agenda.d.ts +4 -0
  46. package/dist/services/agenda.js +17 -7
  47. package/dist/store/exports.d.ts +1 -0
  48. package/dist/store/exports.js +1 -0
  49. package/dist/store/sqlite/email-activity.d.ts +62 -0
  50. package/dist/store/sqlite/email-activity.js +71 -0
  51. package/dist/store/sqlite/email-locations.d.ts +1 -0
  52. package/dist/store/sqlite/email-locations.js +4 -0
  53. package/dist/store/sqlite/emails.d.ts +9 -3
  54. package/dist/store/sqlite/emails.js +7 -14
  55. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { formatLocator, isLocator, parseLocator } from "../domain/locator.js";
3
3
  import { formatReference } from "../domain/references.js";
4
4
  import { agenda } from "../entities/agenda.js";
5
5
  import { AGENDA_COLUMNS, inboxKey, queryFilter, } from "../store/store.js";
6
+ import { mailActivity } from "./agenda-mail.js";
6
7
  import { chatCoverage } from "./chat-coverage.js";
7
8
  import { CHAT_KINDS } from "./chats.js";
8
9
  import { taskAccount } from "./task-rules.js";
@@ -36,6 +37,10 @@ export const AGENDA_QUERY = {
36
37
  }),
37
38
  defaults: ["chat_kind:(dialog OR group)", "claimed:(none OR mine)"],
38
39
  };
40
+ /**
41
+ * An email account's chats are its threads, and whose turn one is follows `mailTurn` (`../mail/owed.ts`), not merely
42
+ * who wrote last: mail sent to many is never owed.
43
+ */
39
44
  export const agendaService = (deps, now = Date.now) => ({
40
45
  show: async ({ dueWithin = DAY, waiting = AGENDA_DEFAULTS.waiting, unanswered = AGENDA_DEFAULTS.unanswered, within = 30 * DAY, query, holder, } = {}) => {
41
46
  const [store, account] = [await deps.store(), await deps.account()];
@@ -91,9 +96,12 @@ export const agendaService = (deps, now = Date.now) => ({
91
96
  createdAt: task.createdAt,
92
97
  assignees,
93
98
  }));
94
- const where = parts.chats ? queryFilter(parts.chats, AGENDA_COLUMNS, { command: "tasks agenda" }) : undefined;
95
- const activity = await store.chats.chatActivity(account, at - within, where);
99
+ const mail = account.provider === "email" ? await mailActivity(store, account, at - within, parts.chats) : undefined;
100
+ const activity = mail ??
101
+ (await store.chats.chatActivity(account, at - within, parts.chats ? queryFilter(parts.chats, AGENDA_COLUMNS, { command: "tasks agenda" }) : undefined));
102
+ const turnOf = (chat) => "turn" in chat ? chat.turn : chat.lastFromOwner === null ? null : chat.lastFromOwner ? "waiting" : "owed";
96
103
  const chats = activity.chats.map(({ lastMessageId, ...chat }) => ({
104
+ turn: turnOf({ lastMessageId, ...chat }),
97
105
  ...chat,
98
106
  lastMessage: lastMessageId === null
99
107
  ? null
@@ -107,12 +115,14 @@ export const agendaService = (deps, now = Date.now) => ({
107
115
  }));
108
116
  const silentFor = (chat) => at - Date.parse(chat.lastMessageAt);
109
117
  const shown = {
110
- owed: chats.filter((chat) => chat.lastFromOwner === false && silentFor(chat) >= unanswered),
111
- waiting: chats.filter((chat) => chat.lastFromOwner === true && silentFor(chat) >= waiting),
112
- unsynced: chats.filter((chat) => chat.lastMessage === null),
118
+ owed: chats.filter((chat) => chat.turn === "owed" && silentFor(chat) >= unanswered),
119
+ waiting: chats.filter((chat) => chat.turn === "waiting" && silentFor(chat) >= waiting),
120
+ // A thread with no stored email but drafts is not behind: mail sync stores every email it lists.
121
+ unsynced: mail ? [] : chats.filter((chat) => chat.lastMessage === null),
113
122
  };
114
- const covered = new Map((await chatCoverage(store, account, Object.values(shown).flatMap((list) => list.map(({ chatId }) => chatId)))).map((one) => [one.chatId, one]));
115
- const withCoverage = (list) => list.map((chat) => ({ ...chat, coverage: covered.get(chat.chatId) }));
123
+ const ids = Object.values(shown).flatMap((list) => list.map(({ chatId }) => chatId));
124
+ const covered = new Map((mail ? mail.coverage(ids) : await chatCoverage(store, account, ids)).map((one) => [one.chatId, one]));
125
+ const withCoverage = (list) => list.map(({ turn: _, ...chat }) => ({ ...chat, coverage: covered.get(chat.chatId) }));
116
126
  const inList = new Set((await store.tasks.categories()).map(({ name }) => name));
117
127
  const unlisted = new Map();
118
128
  for (const { category } of open)
@@ -23,6 +23,7 @@ export { storeClients } from "./sqlite/clients.js";
23
23
  export { CHAT_LIST_KEY, type ChatCompleteness, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
24
24
  export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
25
25
  export type { CounterTarget } from "./sqlite/counters.js";
26
+ export { type EmailMarks, MAIL_AGENDA_COLUMNS, type NewestEmail, type ThreadActivity, } from "./sqlite/email-activity.js";
26
27
  export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
27
28
  export { orphanPointers } from "./sqlite/entity-types.js";
28
29
  export { involvementStoreOver } from "./sqlite/involvements.js";
@@ -6,6 +6,7 @@ export { CHAT_COLUMNS, TRACKED_CHAT_COLUMNS, titlePart } from "./sqlite/chats.js
6
6
  export { storeClients } from "./sqlite/clients.js";
7
7
  export { CHAT_LIST_KEY, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
8
8
  export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
9
+ export { MAIL_AGENDA_COLUMNS, } from "./sqlite/email-activity.js";
9
10
  export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
10
11
  export { orphanPointers } from "./sqlite/entity-types.js";
11
12
  export { involvementStoreOver } from "./sqlite/involvements.js";
@@ -0,0 +1,62 @@
1
+ import type { CacheDatabase } from "../driver.js";
2
+ import type { Where } from "./query-filter.js";
3
+ /** What decides whose turn a thread is, read from its newest email that is not a draft; never its text. */
4
+ export interface NewestEmail {
5
+ externalId: string;
6
+ /** ms: when it was received, or sent when that is unknown. */
7
+ at: number;
8
+ outgoing: boolean;
9
+ /** The mailbox's own address is in To or Cc. */
10
+ addressed: boolean;
11
+ /** Filed in the mailbox of mail sent to many. */
12
+ bulk: boolean;
13
+ /** Carries a List-Id. */
14
+ list: boolean;
15
+ /** Recorded by an envelope rule, without its text. */
16
+ envelope: boolean;
17
+ /** Its sender's mail looks like spam, a scam or a robot (the sender score). */
18
+ demoted: boolean;
19
+ }
20
+ /** One thread with mail after a time: `tasks agenda` and `review` read this, never the emails' text. */
21
+ export interface ThreadActivity {
22
+ /** The thread's external id: what a `chat:email/…` reference names. */
23
+ threadId: string;
24
+ subject: string | null;
25
+ /** ms. */
26
+ lastEmailAt: number;
27
+ emails: number;
28
+ /** `null` when every email of the thread is a draft. */
29
+ newest: NewestEmail | null;
30
+ }
31
+ /** What a reader sorts mail by, per email: never its text. */
32
+ export interface EmailMarks {
33
+ /** Filed in the mailbox of mail sent to many. */
34
+ bulk: boolean;
35
+ listId: string | null;
36
+ /** The mail rule that decided how it is stored, by its id in `rules list`. */
37
+ ruleId: number | null;
38
+ }
39
+ export interface MailActivityStore {
40
+ /** Each live email's marks, by its Message-ID; one the store does not hold is left out. */
41
+ emailMarks(accountId: number, externalIds: readonly string[]): Promise<Map<string, EmailMarks>>;
42
+ /**
43
+ * Each live thread of the account whose newest email is after `since` (ms), newest first, with the facts of that
44
+ * email. `where` is a `tasks agenda` query compiled over `MAIL_AGENDA_COLUMNS`.
45
+ */
46
+ threadActivity(accountId: number, since: number, where?: Where): Promise<ThreadActivity[]>;
47
+ }
48
+ /** `tasks agenda` fields over `threadActivity`'s threads: an email thread counts as a dialog. */
49
+ export declare const MAIL_AGENDA_COLUMNS: {
50
+ readonly chat_kind: {
51
+ readonly type: "enum";
52
+ readonly sql: "'dialog'";
53
+ };
54
+ };
55
+ export declare const threadActivity: (database: CacheDatabase, accountId: number, since: number, where?: Where) => ThreadActivity[];
56
+ /** A thread's live emails, oldest first; `window` keeps those received in (after, until], in ms. */
57
+ export declare const threadEmailIds: (database: CacheDatabase, accountId: number, thread: string, { after, until }?: {
58
+ after?: number;
59
+ until?: number;
60
+ }) => number[];
61
+ export declare const emailMarks: (database: CacheDatabase, accountId: number, externalIds: readonly string[]) => Map<string, EmailMarks>;
62
+ export declare const mailActivity: (database: CacheDatabase) => MailActivityStore;
@@ -0,0 +1,71 @@
1
+ import { int, str } from "./events.js";
2
+ /** `tasks agenda` fields over `threadActivity`'s threads: an email thread counts as a dialog. */
3
+ export const MAIL_AGENDA_COLUMNS = { chat_kind: { type: "enum", sql: "'dialog'" } };
4
+ // The thread's own time is the Date header where sync stored one, so a thread is picked by it; the newest email is
5
+ // picked by when it arrived, which is what an answer follows.
6
+ const ACTIVITY = `
7
+ SELECT t.external_id AS thread, t.subject, t.last_email_at, t.emails_count,
8
+ e.external_id, coalesce(e.received_at, e.sent_at) AS at,
9
+ coalesce(e.outgoing, lower(e.from_address) = lower(a.external_id)) AS outgoing,
10
+ EXISTS (SELECT 1 FROM email_recipients r WHERE r.email_id = e.id AND r.role IN ('to', 'cc')
11
+ AND lower(r.address) = lower(a.external_id)) AS addressed,
12
+ EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
13
+ WHERE m.email_id = e.id AND b.kind = 'bulk') AS bulk,
14
+ coalesce(json_extract(e.headers, '$."list-id"'), json_extract(e.metadata, '$.listId')) IS NOT NULL AS list,
15
+ EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
16
+ WHERE m.email_id = e.id AND b.kind = 'envelope') AS envelope,
17
+ EXISTS (SELECT 1 FROM account_identities ai WHERE ai.account_id = t.account_id
18
+ AND ai.identity_id = e.from_identity_id AND ai.mail_demoted = 1) AS demoted
19
+ FROM email_threads t JOIN accounts a ON a.id = t.account_id
20
+ LEFT JOIN emails e ON e.id = (SELECT f.id FROM emails f WHERE f.email_thread_id = t.id AND f.deleted_at IS NULL
21
+ AND coalesce(f.draft, 0) = 0 ORDER BY coalesce(f.received_at, f.sent_at) DESC, f.id DESC LIMIT 1)
22
+ WHERE t.account_id = ? AND t.deleted_at IS NULL AND t.last_email_at > ?`;
23
+ export const threadActivity = (database, accountId, since, where) => database
24
+ .prepare(`${ACTIVITY}${where ? ` AND (${where.sql})` : ""} ORDER BY t.last_email_at DESC, t.id DESC`)
25
+ .all(accountId, since, ...(where?.params ?? []))
26
+ .map((row) => ({
27
+ threadId: String(row.thread),
28
+ subject: str(row.subject),
29
+ lastEmailAt: Number(row.last_email_at),
30
+ emails: Number(row.emails_count),
31
+ newest: row.external_id == null
32
+ ? null
33
+ : {
34
+ externalId: String(row.external_id),
35
+ at: int(row.at) ?? Number(row.last_email_at),
36
+ outgoing: Boolean(row.outgoing),
37
+ addressed: Boolean(row.addressed),
38
+ bulk: Boolean(row.bulk),
39
+ list: Boolean(row.list),
40
+ envelope: Boolean(row.envelope),
41
+ demoted: Boolean(row.demoted),
42
+ },
43
+ }));
44
+ /** A thread's live emails, oldest first; `window` keeps those received in (after, until], in ms. */
45
+ export const threadEmailIds = (database, accountId, thread, { after, until } = {}) => database
46
+ .prepare(`SELECT e.id FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
47
+ WHERE t.account_id = ? AND t.external_id = ? AND e.deleted_at IS NULL
48
+ AND coalesce(e.received_at, e.sent_at, 0) > coalesce(?, -1)
49
+ AND coalesce(e.received_at, e.sent_at, 0) <= coalesce(?, 9e15)
50
+ ORDER BY coalesce(e.sent_at, e.received_at), e.id`)
51
+ .all(accountId, thread, after ?? null, until ?? null)
52
+ .map(({ id }) => Number(id));
53
+ const MARKS = `
54
+ SELECT e.mail_rule_id, coalesce(json_extract(e.headers, '$."list-id"'), json_extract(e.metadata, '$.listId')) AS list_id,
55
+ EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
56
+ WHERE m.email_id = e.id AND b.kind = 'bulk') AS bulk
57
+ FROM emails e WHERE e.account_id = ? AND e.external_id = ? AND e.deleted_at IS NULL`;
58
+ export const emailMarks = (database, accountId, externalIds) => {
59
+ const one = database.prepare(MARKS);
60
+ const marks = new Map();
61
+ for (const id of externalIds) {
62
+ const row = one.get(accountId, id);
63
+ if (row)
64
+ marks.set(id, { bulk: Boolean(row.bulk), listId: str(row.list_id), ruleId: int(row.mail_rule_id) });
65
+ }
66
+ return marks;
67
+ };
68
+ export const mailActivity = (database) => ({
69
+ emailMarks: async (accountId, externalIds) => emailMarks(database, accountId, externalIds),
70
+ threadActivity: async (accountId, since, where) => threadActivity(database, accountId, since, where),
71
+ });
@@ -59,3 +59,4 @@ export declare const partialMail: (database: CacheDatabase) => PartialMailStore;
59
59
  export declare const storedWhole: (database: CacheDatabase, emailId: number) => boolean;
60
60
  export declare const setLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, found: ReadonlyMap<string, number[]>) => number;
61
61
  export declare const forgetLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, uids: number[]) => void;
62
+ export declare const backfillSentAt: (database: CacheDatabase, accountId: number, limit: number) => number;
@@ -107,3 +107,7 @@ export const forgetLocations = (database, accountId, folder, uidvalidity, uids)
107
107
  for (const emailId of new Set(emails))
108
108
  leave.run(emailId, Number(box.id), emailId, Number(box.id));
109
109
  };
110
+ export const backfillSentAt = (database, accountId, limit) => Number(database
111
+ .prepare(`UPDATE emails SET sent_at = received_at WHERE id IN (
112
+ SELECT id FROM emails WHERE account_id = ? AND sent_at IS NULL AND received_at IS NOT NULL LIMIT ?)`)
113
+ .run(accountId, limit).changes);
@@ -3,6 +3,7 @@ import type { Attachment, Metadata } from "../../meetings/index.js";
3
3
  import type { Stemmer, Stemmers } from "../../search/stem.js";
4
4
  import type { CacheDatabase } from "../driver.js";
5
5
  import type { ConversationSummary, StoredHit } from "../types.js";
6
+ import { type MailActivityStore } from "./email-activity.js";
6
7
  import { type EmailLocationInput, type FolderEmail, type PartialMailStore } from "./email-locations.js";
7
8
  import { type MailRulesStore } from "./mail-rules.js";
8
9
  import { type AttachmentInput } from "./meetings.js";
@@ -205,7 +206,7 @@ export interface SenderScore {
205
206
  demoted?: boolean;
206
207
  }
207
208
  /** Mail in tables of its own: threads, emails, recipients and mailboxes, keyed by account and Message-ID. */
208
- export interface MailStore extends PartialMailStore {
209
+ export interface MailStore extends PartialMailStore, MailActivityStore {
209
210
  saveThread(input: ThreadSave): Promise<ThreadDetails>;
210
211
  /** Several threads and sync state in one transaction: a slice of mail commits with its resume point, or not at all. */
211
212
  saveThreads(inputs: ThreadSave[], states?: SyncStateSave[]): Promise<ThreadDetails[]>;
@@ -238,6 +239,8 @@ export interface MailStore extends PartialMailStore {
238
239
  read?: boolean;
239
240
  flagged?: boolean;
240
241
  }>, now: number): Promise<number>;
242
+ /** Gives up to `limit` emails stored without `sent_at` their internal date; answers how many. */
243
+ backfillSentAt(accountId: number, limit: number): Promise<number>;
241
244
  /** The live emails whose location in `folder` has this UIDVALIDITY, oldest UID first. */
242
245
  folderEmails(accountId: number, folder: string, uidvalidity: string): Promise<FolderEmail[]>;
243
246
  /**
@@ -266,8 +269,11 @@ export interface MailStore extends PartialMailStore {
266
269
  }[]>;
267
270
  /** Live emails of one account by Message-ID, each placed in its thread; an unknown one is left out. */
268
271
  inThreads(accountId: number, externalIds: string[]): Promise<EmailInThread[]>;
269
- /** A thread's live emails, oldest first, as search hits: whole text, people, labels and locator. */
270
- threadHits(accountId: number, threadExternalId: string): Promise<StoredHit[]>;
272
+ /** A thread's live emails, oldest first, as search hits; `window` keeps those received in (after, until], in ms. */
273
+ threadHits(accountId: number, thread: string, window?: {
274
+ after?: number;
275
+ until?: number;
276
+ }): Promise<StoredHit[]>;
271
277
  /** The emails of one account nearest in meaning to `query`, each by its best chunk, best first. */
272
278
  nearest(model: string, query: Float32Array, options: {
273
279
  accountId: number;
@@ -1,8 +1,9 @@
1
1
  import { formatLocator } from "../../domain/locator.js";
2
2
  import { formatReference } from "../../domain/references.js";
3
3
  import { fold, normalize } from "../normalize.js";
4
+ import { mailActivity, threadEmailIds } from "./email-activity.js";
4
5
  import { chunkAttachments, drainEmailIndex, dropAttachmentChunks, MAIL_PIECES, queueThreads, threadPieces, titleOf, } from "./email-index.js";
5
- import { folderEmails, forgetLocations, locate, partialMail, setLocations, storedWhole, } from "./email-locations.js";
6
+ import { backfillSentAt, folderEmails, forgetLocations, locate, partialMail, setLocations, storedWhole, } from "./email-locations.js";
6
7
  import { markEmailsDeleted, markEmailsRestored, recountEmailThread as recount, setEmailFlags } from "./email-state.js";
7
8
  import { bool, flag, fromJson, int, page, str, toJson } from "./events.js";
8
9
  import { folded } from "./folded.js";
@@ -472,6 +473,7 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
472
473
  }));
473
474
  },
474
475
  ...partialMail(database),
476
+ ...mailActivity(database),
475
477
  async emailFacts(filter = {}) {
476
478
  const [where, params] = emailWhere(filter);
477
479
  return database
@@ -554,11 +556,8 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
554
556
  return count;
555
557
  });
556
558
  },
557
- async setFlags(accountId, flags, now) {
558
- return write(database, () => {
559
- return setEmailFlags(database, accountId, flags, now);
560
- });
561
- },
559
+ setFlags: async (accountId, flags, now) => write(database, () => setEmailFlags(database, accountId, flags, now)),
560
+ backfillSentAt: async (accountId, limit) => write(database, () => backfillSentAt(database, accountId, limit)),
562
561
  folderEmails: async (accountId, folder, uidvalidity) => folderEmails(database, accountId, folder, uidvalidity),
563
562
  setLocations: async (accountId, folder, uidvalidity, found) => write(database, () => setLocations(database, accountId, folder, uidvalidity, found)),
564
563
  forgetLocations: async (accountId, folder, uidvalidity, uids) => write(database, () => forgetLocations(database, accountId, folder, uidvalidity, uids)),
@@ -634,14 +633,8 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
634
633
  return place ? [{ ...place, score }] : [];
635
634
  });
636
635
  },
637
- async threadHits(accountId, threadExternalId) {
638
- const ids = database
639
- .prepare(`SELECT e.id FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
640
- WHERE t.account_id = ? AND t.external_id = ? AND e.deleted_at IS NULL
641
- ORDER BY coalesce(e.sent_at, e.received_at), e.id`)
642
- .all(accountId, threadExternalId)
643
- .map(({ id }) => Number(id));
644
- return emailHitsByPk(database, ids);
636
+ async threadHits(accountId, thread, window) {
637
+ return emailHitsByPk(database, threadEmailIds(database, accountId, thread, window));
645
638
  },
646
639
  async inThreads(accountId, externalIds) {
647
640
  const id = database.prepare("SELECT id FROM emails WHERE account_id = ? AND external_id = ? AND deleted_at IS NULL");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.553.0",
3
+ "version": "0.555.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",