@wirecat/cli-core 0.562.0 → 0.564.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/CHANGELOG.md +139 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/config-command.js +18 -1
- package/dist/cli/envelope.js +13 -1
- package/dist/cli/execution.js +1 -0
- package/dist/cli/messenger/archive-commands.js +17 -0
- package/dist/cli/messenger/catch-up-answers.d.ts +5 -0
- package/dist/cli/messenger/catch-up-answers.js +3 -0
- package/dist/cli/messenger/reply-stats-command.d.ts +4 -0
- package/dist/cli/messenger/reply-stats-command.js +57 -0
- package/dist/cli/messenger/search-answers.d.ts +1 -0
- package/dist/cli/messenger/search-answers.js +1 -1
- package/dist/cli/messenger/search-command.js +8 -0
- package/dist/cli/messenger/stats-answers.d.ts +6 -3
- package/dist/cli/messenger/stats-answers.js +39 -9
- package/dist/cli/messenger/stats-command.js +5 -0
- package/dist/cli/messenger/tasks-command.js +35 -14
- package/dist/cli/output-budget.d.ts +26 -0
- package/dist/cli/output-budget.js +73 -0
- package/dist/cli/program.js +14 -1
- package/dist/cli/settings.d.ts +5 -0
- package/dist/cli/settings.js +88 -5
- package/dist/cli/shared-command-exports.d.ts +8 -0
- package/dist/conversations/mail-text.d.ts +6 -0
- package/dist/conversations/mail-text.js +73 -0
- package/dist/domain/admin-statistics.d.ts +5 -0
- package/dist/domain/admin-statistics.js +4 -3
- package/dist/domain/calendar.d.ts +2 -0
- package/dist/domain/calendar.js +6 -0
- package/dist/entities/chat-stats.d.ts +16 -2
- package/dist/entities/chat-stats.js +18 -3
- package/dist/entities/index.d.ts +80 -802
- package/dist/entities/index.js +26 -16
- package/dist/entities/mail-counts.d.ts +15 -0
- package/dist/entities/mail-counts.js +16 -0
- package/dist/entities/meeting.d.ts +1 -1
- package/dist/entities/meeting.js +1 -1
- package/dist/entities/reply-stats.d.ts +33 -0
- package/dist/entities/reply-stats.js +19 -0
- package/dist/entities/store-records.d.ts +8 -0
- package/dist/entities/store-records.js +8 -0
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +7 -3
- package/dist/mail/attachments.d.ts +15 -2
- package/dist/mail/attachments.js +19 -3
- package/dist/mail/cli/index.d.ts +22 -1
- package/dist/mail/cli/index.js +28 -4
- package/dist/mail/cli/review.d.ts +5 -2
- package/dist/mail/cli/review.js +58 -43
- package/dist/mail/index.d.ts +4 -0
- package/dist/mail/index.js +4 -0
- package/dist/mail/partial.d.ts +3 -1
- package/dist/mail/partial.js +2 -2
- package/dist/mail/retry.d.ts +3 -1
- package/dist/mail/retry.js +3 -3
- package/dist/mail/stats.d.ts +50 -0
- package/dist/mail/stats.js +60 -0
- package/dist/mail/sync-types.d.ts +3 -0
- package/dist/mail/sync.js +5 -3
- package/dist/mail/whole.d.ts +28 -0
- package/dist/mail/whole.js +70 -0
- package/dist/meetings/index.d.ts +2 -2
- package/dist/meetings/index.js +2 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/read-answers.d.ts +22 -1
- package/dist/meetings/read-answers.js +38 -0
- package/dist/meetings/stats.d.ts +44 -0
- package/dist/meetings/stats.js +77 -7
- package/dist/search/lucene/registry.js +2 -1
- package/dist/search/question-plan.d.ts +16 -0
- package/dist/search/question-plan.js +34 -0
- package/dist/services/events-search.d.ts +5 -1
- package/dist/services/events-search.js +10 -4
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.js +3 -1
- package/dist/services/meeting-stats.d.ts +12 -1
- package/dist/services/meeting-stats.js +12 -0
- package/dist/services/messages-discovery.d.ts +1 -5
- package/dist/services/messages-discovery.js +5 -18
- package/dist/services/messages-meaning.js +25 -3
- package/dist/services/messages.js +4 -3
- package/dist/services/notes-search.d.ts +5 -1
- package/dist/services/notes-search.js +16 -5
- package/dist/services/proposal-tasks.js +3 -12
- package/dist/services/proposals/ids.d.ts +2 -2
- package/dist/services/proposals/ids.js +5 -3
- package/dist/services/proposals/kinds/action.js +3 -2
- package/dist/services/proposals/kinds/findings.d.ts +1 -1
- package/dist/services/proposals/kinds/findings.js +1 -1
- package/dist/services/proposals/kinds/meeting-findings.d.ts +7 -0
- package/dist/services/proposals/kinds/meeting-findings.js +73 -0
- package/dist/services/proposals/kinds/task-close.d.ts +7 -0
- package/dist/services/proposals/kinds/task-close.js +76 -0
- package/dist/services/proposals/registry.js +12 -4
- package/dist/services/proposals/types.d.ts +2 -0
- package/dist/services/reply-stats.d.ts +48 -0
- package/dist/services/reply-stats.js +137 -0
- package/dist/services/search-all.d.ts +5 -0
- package/dist/services/search-all.js +47 -14
- package/dist/services/task-stats.d.ts +41 -0
- package/dist/services/task-stats.js +126 -0
- package/dist/services/tasks.d.ts +17 -4
- package/dist/services/tasks.js +31 -14
- package/dist/store/chats.d.ts +3 -1
- package/dist/store/chats.js +6 -0
- package/dist/store/decisions.d.ts +16 -11
- package/dist/store/decisions.js +11 -2
- package/dist/store/exports.d.ts +1 -1
- package/dist/store/exports.js +1 -1
- package/dist/store/index.d.ts +5 -4
- package/dist/store/index.js +3 -3
- package/dist/store/queries.d.ts +4 -0
- package/dist/store/queries.js +3 -0
- package/dist/store/sqlite/completeness.d.ts +17 -0
- package/dist/store/sqlite/completeness.js +49 -13
- package/dist/store/sqlite/email-locations.d.ts +4 -0
- package/dist/store/sqlite/email-locations.js +26 -11
- package/dist/store/sqlite/lucene-fields.d.ts +2 -0
- package/dist/store/sqlite/lucene-fields.js +5 -1
- package/dist/store/sqlite/lucene-groups.d.ts +15 -0
- package/dist/store/sqlite/lucene-groups.js +21 -0
- package/dist/store/sqlite/lucene.d.ts +2 -9
- package/dist/store/sqlite/lucene.js +1 -12
- package/dist/store/sqlite/mail-findings.js +1 -1
- package/dist/store/sqlite/meeting-findings.js +1 -1
- package/dist/store/sqlite/meeting-stats.d.ts +2 -0
- package/dist/store/sqlite/meeting-stats.js +27 -1
- package/dist/store/sqlite/proposed-actions.d.ts +2 -0
- package/dist/store/sqlite/proposed-actions.js +24 -17
- package/dist/store/sqlite/reply-times.d.ts +57 -0
- package/dist/store/sqlite/reply-times.js +113 -0
- package/dist/store/sqlite/retention.js +2 -7
- package/dist/store/sqlite/task-board.js +2 -0
- package/dist/store/sqlite/task-stats.d.ts +32 -0
- package/dist/store/sqlite/task-stats.js +45 -0
- package/dist/store/sqlite/tasks.d.ts +2 -1
- package/dist/store/sqlite/tasks.js +2 -0
- package/dist/store/sqlite/work-tasks.d.ts +12 -2
- package/dist/store/sqlite/work-tasks.js +35 -26
- package/dist/store/store.d.ts +1 -0
- package/package.json +1 -1
- package/parity.json +37 -1
package/dist/mail/cli/review.js
CHANGED
|
@@ -8,10 +8,12 @@ import { LOCAL_READ } from "../../cli/effects.js";
|
|
|
8
8
|
import { listOf, renderAnswer } from "../../cli/envelope.js";
|
|
9
9
|
import { listQuery, nativeField, nativeTerms, queryHelp, typedQuery } from "../../cli/list-query.js";
|
|
10
10
|
import { pointsOf } from "../../cli/messenger/catch-up.js";
|
|
11
|
-
import { catchUpQuery, summaryOf } from "../../cli/messenger/catch-up-answers.js";
|
|
11
|
+
import { catchUpQuery, SUMMARY_UNTRUSTED, summaryOf } from "../../cli/messenger/catch-up-answers.js";
|
|
12
12
|
import { messageItem } from "../../cli/messenger/message-items.js";
|
|
13
13
|
import { checkPoints } from "../../cli/messenger/points.js";
|
|
14
14
|
import { unansweredHours } from "../../cli/messenger/review.js";
|
|
15
|
+
import { MAIL_HIT_UNTRUSTED } from "../../cli/messenger/search-answers.js";
|
|
16
|
+
import { mailOwnText } from "../../conversations/mail-text.js";
|
|
15
17
|
import { parseDuration } from "../../domain/duration.js";
|
|
16
18
|
import { formatReference } from "../../domain/references.js";
|
|
17
19
|
import { AGENDA_DEFAULTS } from "../../services/agenda.js";
|
|
@@ -20,6 +22,7 @@ import { readPastMark } from "../marks.js";
|
|
|
20
22
|
import { hitText } from "../read.js";
|
|
21
23
|
import { OWED_DAYS, REVIEW_THREADS, reviewMail } from "../review.js";
|
|
22
24
|
const DAY = 86_400_000;
|
|
25
|
+
const REVIEW_UNTRUSTED = [...SUMMARY_UNTRUSTED, ...MAIL_HIT_UNTRUSTED];
|
|
23
26
|
/** Where a mail review starts: a time only; one thread is `conversations show`. */
|
|
24
27
|
export const MAIL_REVIEW_QUERY = defineQuery({
|
|
25
28
|
entity: "message",
|
|
@@ -29,31 +32,44 @@ export const MAIL_REVIEW_QUERY = defineQuery({
|
|
|
29
32
|
nativeField({ name: "bulk", type: "kw", operators: ["term"], example: "bulk:false" }),
|
|
30
33
|
],
|
|
31
34
|
});
|
|
32
|
-
/** The mail review's steps; a host lists them beside its own so its step checks know them. */
|
|
33
|
-
export const
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{ name: "cursor", required: true },
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
/** The mail review's steps where a host mounted them; a host lists them beside its own so its step checks know them. */
|
|
36
|
+
export const mailReviewSteps = (mount = {}) => {
|
|
37
|
+
const mailbox = mount.mailbox ? [{ name: mount.mailbox.option, required: true }] : [];
|
|
38
|
+
return defineSteps([
|
|
39
|
+
{
|
|
40
|
+
id: "mail-messages.acknowledge",
|
|
41
|
+
command: mount.acknowledge ?? "messages acknowledge",
|
|
42
|
+
args: [{ name: "holder", required: true }, { name: "cursor", required: true }, ...mailbox],
|
|
43
|
+
why: "once these are processed, this moves the holder's mark past them",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "mail-review.next",
|
|
47
|
+
command: mount.review ?? "review",
|
|
48
|
+
args: [{ name: "query", required: true }, ...mailbox],
|
|
49
|
+
why: "the next review starts where this one ended",
|
|
50
|
+
},
|
|
51
|
+
...(mount.conversation === null
|
|
52
|
+
? []
|
|
53
|
+
: [
|
|
54
|
+
{
|
|
55
|
+
id: "mail-conversations.show",
|
|
56
|
+
command: mount.conversation ?? "conversations show",
|
|
57
|
+
args: [{ name: "conversation", required: true }, ...mailbox],
|
|
58
|
+
why: "only the newest emails of the thread were read here; this reads it whole",
|
|
59
|
+
},
|
|
60
|
+
]),
|
|
61
|
+
]);
|
|
62
|
+
};
|
|
63
|
+
/** The steps of a mail CLI, whose profile names the mailbox. */
|
|
64
|
+
export const MAIL_REVIEW_STEPS = mailReviewSteps();
|
|
65
|
+
const stepsFor = (mount, mailbox) => {
|
|
66
|
+
const definitions = mailReviewSteps(mount);
|
|
67
|
+
const where = mount?.mailbox ? { [mount.mailbox.option]: mount.mailbox.value(mailbox) } : {};
|
|
68
|
+
return (id, args) => {
|
|
69
|
+
const definition = definitions.find((one) => one.id === id);
|
|
70
|
+
return definition ? [stepOf(definition, { args: { ...args, ...where } })] : [];
|
|
71
|
+
};
|
|
72
|
+
};
|
|
57
73
|
const waitingHours = (value) => value === true ? AGENDA_DEFAULTS.waiting / 3_600_000 : parseDuration(String(value), "--waiting") / 3_600_000;
|
|
58
74
|
const quoted = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
59
75
|
/** A file name for the mailbox's points: they describe the mailbox, whichever profile reads it. */
|
|
@@ -67,20 +83,21 @@ const markText = (marks) => {
|
|
|
67
83
|
const shown = said.filter(Boolean).join(" · ");
|
|
68
84
|
return shown ? `\n [${shown}]` : "";
|
|
69
85
|
};
|
|
70
|
-
const reviewAnswer = (found, mailbox, fresh) => {
|
|
86
|
+
const reviewAnswer = (found, mailbox, fresh, step) => {
|
|
71
87
|
const read = found.chats
|
|
72
88
|
.flatMap((chat) => chat.messages.map((message) => ({ ...message, chatTitle: chat.title })))
|
|
73
89
|
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
74
90
|
const cut = found.chats.filter((chat) => chat.more);
|
|
75
91
|
const ref = (thread) => formatReference({ type: "chat", ...mailbox, chat: thread });
|
|
76
92
|
const turns = found.unanswered !== undefined || found.waiting !== undefined;
|
|
93
|
+
const wholeThreads = cut.flatMap((chat) => step("mail-conversations.show", { conversation: ref(chat.id) }));
|
|
77
94
|
const missing = [
|
|
78
95
|
...(cut.length > 0
|
|
79
96
|
? [
|
|
80
97
|
{
|
|
81
98
|
field: "items",
|
|
82
99
|
why: `only the newest emails of ${cut.length} thread(s)`,
|
|
83
|
-
step: "mail-conversations.show",
|
|
100
|
+
...(wholeThreads.length ? { step: "mail-conversations.show" } : {}),
|
|
84
101
|
},
|
|
85
102
|
]
|
|
86
103
|
: []),
|
|
@@ -98,13 +115,9 @@ const reviewAnswer = (found, mailbox, fresh) => {
|
|
|
98
115
|
];
|
|
99
116
|
const next = [
|
|
100
117
|
...(!fresh && !turns
|
|
101
|
-
?
|
|
102
|
-
stepOf(step("mail-review.next"), {
|
|
103
|
-
args: { query: `date>${quoted(found.complete ? found.until : found.since)}` },
|
|
104
|
-
}),
|
|
105
|
-
]
|
|
118
|
+
? step("mail-review.next", { query: `date>${quoted(found.complete ? found.until : found.since)}` })
|
|
106
119
|
: []),
|
|
107
|
-
...
|
|
120
|
+
...wholeThreads,
|
|
108
121
|
];
|
|
109
122
|
return listOf("review", {
|
|
110
123
|
items: read.map((message) => ({
|
|
@@ -122,6 +135,7 @@ const reviewAnswer = (found, mailbox, fresh) => {
|
|
|
122
135
|
}
|
|
123
136
|
: {}),
|
|
124
137
|
},
|
|
138
|
+
untrusted: REVIEW_UNTRUSTED,
|
|
125
139
|
})),
|
|
126
140
|
limit: Math.max(1, read.length),
|
|
127
141
|
has_more: false,
|
|
@@ -142,8 +156,10 @@ const holderReview = async (command, mail, holder, query) => {
|
|
|
142
156
|
: new Map();
|
|
143
157
|
return { read, mailbox, marks };
|
|
144
158
|
});
|
|
159
|
+
// An agent reads this: earlier mail and signatures stay out of its text; the fingerprint is over the stored body.
|
|
160
|
+
const emails = read.emails.map((email) => ({ ...email, text: mailOwnText(email.text) }));
|
|
145
161
|
const chats = new Map();
|
|
146
|
-
for (const email of
|
|
162
|
+
for (const email of emails) {
|
|
147
163
|
const chat = chats.get(email.chatId) ?? {
|
|
148
164
|
id: email.chatId,
|
|
149
165
|
title: email.chatTitle,
|
|
@@ -165,12 +181,10 @@ const holderReview = async (command, mail, holder, query) => {
|
|
|
165
181
|
partial: false,
|
|
166
182
|
quiet: 0,
|
|
167
183
|
};
|
|
168
|
-
const
|
|
184
|
+
const step = stepsFor(mail.mount, mailbox);
|
|
185
|
+
const answer = reviewAnswer(found, mailbox, true, step);
|
|
169
186
|
const skipped = Object.entries(read.skipped).filter(([, count]) => count > 0);
|
|
170
|
-
answer.next = [
|
|
171
|
-
stepOf(step("mail-messages.acknowledge"), { args: { holder, cursor: read.cursor } }),
|
|
172
|
-
...(answer.next ?? []),
|
|
173
|
-
];
|
|
187
|
+
answer.next = [...step("mail-messages.acknowledge", { holder, cursor: read.cursor }), ...(answer.next ?? [])];
|
|
174
188
|
answer.missing = [
|
|
175
189
|
...(answer.missing ?? []),
|
|
176
190
|
...(read.held > 0 ? [{ field: "items", why: `${read.held} partial emails held until they are whole` }] : []),
|
|
@@ -183,7 +197,7 @@ const holderReview = async (command, mail, holder, query) => {
|
|
|
183
197
|
output.renderer.note(read.mark
|
|
184
198
|
? `${holder}: past ${read.mark.throughId}; acknowledge with --cursor ${read.cursor}${read.more ? "; more after these" : ""}`
|
|
185
199
|
: `${holder} has no mark yet: it starts from now — acknowledge with --cursor ${read.cursor}`);
|
|
186
|
-
return
|
|
200
|
+
return emails.map((hit) => `${hitText(hit)}${markText(marks.get(hit.id))}\n`).join("");
|
|
187
201
|
});
|
|
188
202
|
};
|
|
189
203
|
/**
|
|
@@ -209,7 +223,8 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
|
|
|
209
223
|
.option("--unanswered [duration]", "only threads whose newest email came to you, in To or Cc, with no answer for this long — 4h, 1d; 24h if not given")
|
|
210
224
|
.option("--waiting [duration]", "only threads where you wrote last and nobody answered for this long — 3d; 7d if not given")
|
|
211
225
|
.option("--holder <name>", "read for this holder — an agent or a pipeline — what is stored past its processing mark, by store order; " +
|
|
212
|
-
"bulk, envelopes, drafts, suspicious senders and partial mail left out;
|
|
226
|
+
"bulk, envelopes, drafts, suspicious senders and partial mail left out; each text without quoted earlier mail " +
|
|
227
|
+
"or the signature; `messages acknowledge` moves the mark")
|
|
213
228
|
.option("--new", "what changed since the last `review --new` of this mailbox, a point per thread — for scheduled runs")
|
|
214
229
|
.addHelpText("after", `
|
|
215
230
|
Without a date, --unanswered and --waiting look at threads active in the last ${OWED_DAYS} days.
|
|
@@ -254,7 +269,7 @@ an agent over MCP, and each scheduled run.`)
|
|
|
254
269
|
return { found, mailbox, saved };
|
|
255
270
|
});
|
|
256
271
|
const output = outputFor(this);
|
|
257
|
-
const answer = reviewAnswer(found, mailbox, saved !== undefined);
|
|
272
|
+
const answer = reviewAnswer(found, mailbox, saved !== undefined, stepsFor(mail.mount, mailbox));
|
|
258
273
|
if (meta)
|
|
259
274
|
answer.meta = { ...meta, ...answer.meta };
|
|
260
275
|
renderAnswer(this, output, answer, () => {
|
package/dist/mail/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { mailOwnText } from "../conversations/mail-text.js";
|
|
1
2
|
export { mailAccountKey } from "./account-key.js";
|
|
3
|
+
export { DEFAULT_ATTACHMENT_POLICY, type MailAttachmentPolicy, POLICY_SKIPS } from "./attachments.js";
|
|
2
4
|
export { BULK, bulkBox } from "./bulk.js";
|
|
3
5
|
export { factsOf } from "./facts.js";
|
|
4
6
|
export { MAIL_FINDINGS_KIND, MAIL_FINDINGS_PER_RUN, MAIL_FINDINGS_PER_SENDER, type MailFinding, type MailFindingsInput, mailFindingFingerprint, validateMailFindings, } from "./findings.js";
|
|
@@ -27,5 +29,7 @@ export { type MailCriteria, mailCriteriaOf, narrows } from "./server-query.js";
|
|
|
27
29
|
export { type MailServerAccess, type MailServerRequest, searchMailServer } from "./server-search.js";
|
|
28
30
|
export type { Listed, MailSource, Quick, Searched } from "./source.js";
|
|
29
31
|
export { SEARCH_REFUSED, searchRefused } from "./source.js";
|
|
32
|
+
export { MAIL_STATS_BY, type MailStats, type MailStatsBy, type MailStatsRequest, type MailStatsRow, mailStats, mailStatsItem, } from "./stats.js";
|
|
30
33
|
export { listedOf, type StoredMail } from "./stored.js";
|
|
31
34
|
export { MAX_ATTACHMENT_CHARS, type MailAccount, type MailIndexed, type MailSyncOptions, type MailSyncResult, RETRYABLE_ATTACHMENTS, syncMail, } from "./sync.js";
|
|
35
|
+
export { readEmailWhole, type WholeAttachment } from "./whole.js";
|
package/dist/mail/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { mailOwnText } from "../conversations/mail-text.js";
|
|
1
2
|
export { mailAccountKey } from "./account-key.js";
|
|
3
|
+
export { DEFAULT_ATTACHMENT_POLICY, POLICY_SKIPS } from "./attachments.js";
|
|
2
4
|
export { BULK, bulkBox } from "./bulk.js";
|
|
3
5
|
export { factsOf } from "./facts.js";
|
|
4
6
|
export { MAIL_FINDINGS_KIND, MAIL_FINDINGS_PER_RUN, MAIL_FINDINGS_PER_SENDER, mailFindingFingerprint, validateMailFindings, } from "./findings.js";
|
|
@@ -26,5 +28,7 @@ export { DEMOTE_AT, demotedSenders, SCORE_VERSION, scoreSenders, } from "./sende
|
|
|
26
28
|
export { mailCriteriaOf, narrows } from "./server-query.js";
|
|
27
29
|
export { searchMailServer } from "./server-search.js";
|
|
28
30
|
export { SEARCH_REFUSED, searchRefused } from "./source.js";
|
|
31
|
+
export { MAIL_STATS_BY, mailStats, mailStatsItem, } from "./stats.js";
|
|
29
32
|
export { listedOf } from "./stored.js";
|
|
30
33
|
export { MAX_ATTACHMENT_CHARS, RETRYABLE_ATTACHMENTS, syncMail, } from "./sync.js";
|
|
34
|
+
export { readEmailWhole } from "./whole.js";
|
package/dist/mail/partial.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BudgetStop } from "../background/budget.js";
|
|
2
2
|
import type { LoadEngine } from "../readers/types.js";
|
|
3
3
|
import type { MessageStore } from "../store/index.js";
|
|
4
|
+
import { type MailAttachmentPolicy } from "./attachments.js";
|
|
4
5
|
import type { MailSource } from "./source.js";
|
|
5
6
|
/** The `emails.metadata` key of an email stored from its first bytes: `{ bytes }` read of its `size`. */
|
|
6
7
|
export declare const PARTIAL = "partial";
|
|
@@ -23,7 +24,7 @@ export interface PartialOutcome {
|
|
|
23
24
|
* `identify` gives the stored id for the bytes read there. Each failed read is counted, and after `PARTIAL_TRIES`, or
|
|
24
25
|
* at once when the place holds another message, the email is left partial for good.
|
|
25
26
|
*/
|
|
26
|
-
export declare const completePartial: ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, take, signal, }: {
|
|
27
|
+
export declare const completePartial: ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, policy, loadEngine, now, take, signal, }: {
|
|
27
28
|
store: MessageStore;
|
|
28
29
|
source: MailSource;
|
|
29
30
|
accountId: number;
|
|
@@ -32,6 +33,7 @@ export declare const completePartial: ({ store, source, accountId, address, fold
|
|
|
32
33
|
folders: () => Promise<readonly string[]>;
|
|
33
34
|
maxBodyChars: number;
|
|
34
35
|
maxAttachmentChars: number;
|
|
36
|
+
policy?: MailAttachmentPolicy;
|
|
35
37
|
loadEngine: LoadEngine;
|
|
36
38
|
now: number;
|
|
37
39
|
/** A budgeted run's next request, or why there is none. */
|
package/dist/mail/partial.js
CHANGED
|
@@ -15,7 +15,7 @@ export const PARTIAL_TRIES = 3;
|
|
|
15
15
|
* `identify` gives the stored id for the bytes read there. Each failed read is counted, and after `PARTIAL_TRIES`, or
|
|
16
16
|
* at once when the place holds another message, the email is left partial for good.
|
|
17
17
|
*/
|
|
18
|
-
export const completePartial = async ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, take, signal, }) => {
|
|
18
|
+
export const completePartial = async ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, policy, loadEngine, now, take, signal, }) => {
|
|
19
19
|
const { emails, total, stuck } = await store.mail.partialEmails(accountId, PARTIAL_PER_RUN);
|
|
20
20
|
if (emails.length === 0)
|
|
21
21
|
return { completed: 0, left: 0, stuck };
|
|
@@ -89,7 +89,7 @@ export const completePartial = async ({ store, source, accountId, address, folde
|
|
|
89
89
|
}
|
|
90
90
|
try {
|
|
91
91
|
const mail = await parseMail(raw, maxBodyChars);
|
|
92
|
-
const files = await readAttachments(mail, email.externalId, { loadEngine, maxAttachmentChars, at: now, ...(signal ? { signal } : {}) }, attachments);
|
|
92
|
+
const files = await readAttachments(mail, email.externalId, { loadEngine, maxAttachmentChars, at: now, ...(policy ? { policy } : {}), ...(signal ? { signal } : {}) }, attachments);
|
|
93
93
|
const { [PARTIAL]: _partial, ...metadata } = email.metadata ?? {};
|
|
94
94
|
const whole = { ...item, messageId: email.externalId, threadId: email.threadExternalId };
|
|
95
95
|
await store.mail.saveThread({
|
package/dist/mail/retry.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LoadEngine } from "../readers/types.js";
|
|
2
2
|
import type { MessageStore } from "../store/index.js";
|
|
3
|
+
import { type MailAttachmentPolicy } from "./attachments.js";
|
|
3
4
|
import type { MailSource } from "./source.js";
|
|
4
5
|
import type { MailAccount, MailSyncOptions, MailSyncResult } from "./sync.js";
|
|
5
6
|
/** Failures a newer install mends: the reader was missing, not the file at fault, so reading again can succeed. */
|
|
@@ -9,7 +10,7 @@ export declare const RETRYABLE_ATTACHMENTS: string[];
|
|
|
9
10
|
* attachment file, so the email comes from the source again. Nothing is listed, so no deletion is proved and no
|
|
10
11
|
* resume point moves; the email keeps its stored mailboxes, flags and rule.
|
|
11
12
|
*/
|
|
12
|
-
export declare const retryUnread: ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, now, loadEngine, index, }: {
|
|
13
|
+
export declare const retryUnread: ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, policy, now, loadEngine, index, }: {
|
|
13
14
|
store: MessageStore;
|
|
14
15
|
source: MailSource;
|
|
15
16
|
account: MailAccount;
|
|
@@ -17,6 +18,7 @@ export declare const retryUnread: ({ store, source, account, since, max, maxBody
|
|
|
17
18
|
max: number;
|
|
18
19
|
maxBodyChars: number;
|
|
19
20
|
maxAttachmentChars: number;
|
|
21
|
+
policy?: MailAttachmentPolicy;
|
|
20
22
|
now: () => number;
|
|
21
23
|
loadEngine: LoadEngine;
|
|
22
24
|
index: MailSyncOptions["index"];
|
package/dist/mail/retry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { mailAccountKey } from "./account-key.js";
|
|
3
|
-
import { addressOf, FETCH_CHUNK, readAttachments } from "./attachments.js";
|
|
3
|
+
import { addressOf, FETCH_CHUNK, readAttachments, } from "./attachments.js";
|
|
4
4
|
import { sentAtOf } from "./email-input.js";
|
|
5
5
|
import { mailFolders } from "./folders.js";
|
|
6
6
|
import { mailHeadersOf } from "./headers.js";
|
|
@@ -12,7 +12,7 @@ export const RETRYABLE_ATTACHMENTS = ["engine-missing"];
|
|
|
12
12
|
* attachment file, so the email comes from the source again. Nothing is listed, so no deletion is proved and no
|
|
13
13
|
* resume point moves; the email keeps its stored mailboxes, flags and rule.
|
|
14
14
|
*/
|
|
15
|
-
export const retryUnread = async ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, now, loadEngine, index, }) => {
|
|
15
|
+
export const retryUnread = async ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, policy, now, loadEngine, index, }) => {
|
|
16
16
|
const startedAt = now();
|
|
17
17
|
const key = mailAccountKey(account.address);
|
|
18
18
|
const find = source.find;
|
|
@@ -55,7 +55,7 @@ export const retryUnread = async ({ store, source, account, since, max, maxBodyC
|
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
57
|
const before = new Map(stored.attachments.map(({ position, extractionError }) => [position, extractionError]));
|
|
58
|
-
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt }, attachments);
|
|
58
|
+
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, ...(policy ? { policy } : {}) }, attachments);
|
|
59
59
|
for (const file of files)
|
|
60
60
|
if (RETRYABLE_ATTACHMENTS.includes(before.get(file.position) ?? ""))
|
|
61
61
|
if (file.extraction === "text")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Saved } from "../services/message-resolution.js";
|
|
2
|
+
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
3
|
+
export declare const MAIL_STATS_BY: readonly ["day", "week", "sender"];
|
|
4
|
+
export type MailStatsBy = (typeof MAIL_STATS_BY)[number];
|
|
5
|
+
export interface MailStatsRequest {
|
|
6
|
+
by: MailStatsBy;
|
|
7
|
+
/** The query of `search mail`; every stored email when unset. */
|
|
8
|
+
text?: string;
|
|
9
|
+
/** Only these mailboxes; every stored one when neither this nor `mailAccount` is given. */
|
|
10
|
+
accounts?: AccountKey[];
|
|
11
|
+
/** Only this mailbox, by its address. */
|
|
12
|
+
mailAccount?: string;
|
|
13
|
+
exact?: boolean;
|
|
14
|
+
/** Where calendar days and weeks start; the system timezone when unset, as for search. */
|
|
15
|
+
timezone?: string;
|
|
16
|
+
/** Rows kept: the busiest senders, or the latest days and weeks, oldest first. */
|
|
17
|
+
limit: number;
|
|
18
|
+
}
|
|
19
|
+
export interface MailStatsRow {
|
|
20
|
+
/** `YYYY-MM-DD` (a week by its Monday), or the sender's address. */
|
|
21
|
+
key: string;
|
|
22
|
+
name: string | null;
|
|
23
|
+
received: number;
|
|
24
|
+
sent: number;
|
|
25
|
+
}
|
|
26
|
+
export interface MailStats {
|
|
27
|
+
by: MailStatsBy;
|
|
28
|
+
received: number;
|
|
29
|
+
sent: number;
|
|
30
|
+
rows: MailStatsRow[];
|
|
31
|
+
hasMore: boolean;
|
|
32
|
+
timezone: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stored mail the `search mail` query matches, received and sent, per calendar day, week or sender; counts only.
|
|
36
|
+
* `--by sender` counts received mail: the sender of sent mail is the owner. Reads the store, records no search.
|
|
37
|
+
*/
|
|
38
|
+
export declare const mailStats: (store: MessageStore, runAs: AccountKey, { by, text, accounts, mailAccount, exact, timezone, limit }: MailStatsRequest, saved?: Saved) => Promise<MailStats>;
|
|
39
|
+
/** The `mail_counts` entity: what a command answers as its `item`. */
|
|
40
|
+
export declare const mailStatsItem: ({ by, received, sent, rows }: MailStats) => {
|
|
41
|
+
by: "day" | "sender" | "week";
|
|
42
|
+
received: number;
|
|
43
|
+
sent: number;
|
|
44
|
+
rows: {
|
|
45
|
+
key: string;
|
|
46
|
+
name: string | null;
|
|
47
|
+
received: number;
|
|
48
|
+
sent: number;
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { calendarKey, weekKey } from "../domain/calendar.js";
|
|
2
|
+
import { prepareLucene } from "../services/messages-search.js";
|
|
3
|
+
export const MAIL_STATS_BY = ["day", "week", "sender"];
|
|
4
|
+
/**
|
|
5
|
+
* Stored mail the `search mail` query matches, received and sent, per calendar day, week or sender; counts only.
|
|
6
|
+
* `--by sender` counts received mail: the sender of sent mail is the owner. Reads the store, records no search.
|
|
7
|
+
*/
|
|
8
|
+
export const mailStats = async (store, runAs, { by, text, accounts, mailAccount, exact, timezone, limit }, saved = {}) => {
|
|
9
|
+
const prepared = await prepareLucene(store, runAs, {
|
|
10
|
+
kind: "mail",
|
|
11
|
+
limit: 1,
|
|
12
|
+
...(text?.trim() ? { text } : {}),
|
|
13
|
+
...(accounts === undefined ? {} : { accounts }),
|
|
14
|
+
...(mailAccount === undefined ? {} : { mailAccount }),
|
|
15
|
+
...(exact ? { exact: true } : {}),
|
|
16
|
+
...(timezone === undefined ? {} : { timezone }),
|
|
17
|
+
}, saved);
|
|
18
|
+
const mailboxes = prepared.scopeAccounts.filter(({ provider }) => provider === "email");
|
|
19
|
+
const groups = mailboxes.length
|
|
20
|
+
? await store.queries.countQuery({ ...prepared.execution, accounts: mailboxes, corpus: "mail" }, by === "sender" ? "sender_direction" : "time_direction")
|
|
21
|
+
: [];
|
|
22
|
+
const received = groups.filter(({ outgoing }) => !outgoing).reduce((sum, { count }) => sum + count, 0);
|
|
23
|
+
const sent = groups.filter(({ outgoing }) => outgoing).reduce((sum, { count }) => sum + count, 0);
|
|
24
|
+
let rows;
|
|
25
|
+
if (by === "sender")
|
|
26
|
+
rows = groups
|
|
27
|
+
.filter(({ outgoing }) => !outgoing)
|
|
28
|
+
.map(({ id, name, count }) => ({ key: id ?? "unknown", name, received: count, sent: 0 }))
|
|
29
|
+
.sort((a, b) => b.received - a.received || a.key.localeCompare(b.key));
|
|
30
|
+
else {
|
|
31
|
+
const day = calendarKey(prepared.timezone, "day");
|
|
32
|
+
const keyOf = by === "week" ? (at) => weekKey(day(at)) : day;
|
|
33
|
+
const totals = new Map();
|
|
34
|
+
for (const { id, outgoing, count } of groups) {
|
|
35
|
+
const key = keyOf(Number(id));
|
|
36
|
+
const row = totals.get(key) ?? { key, name: null, received: 0, sent: 0 };
|
|
37
|
+
if (outgoing)
|
|
38
|
+
row.sent += count;
|
|
39
|
+
else
|
|
40
|
+
row.received += count;
|
|
41
|
+
totals.set(key, row);
|
|
42
|
+
}
|
|
43
|
+
rows = [...totals.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
by,
|
|
47
|
+
received,
|
|
48
|
+
sent,
|
|
49
|
+
rows: by === "sender" ? rows.slice(0, limit) : rows.slice(-limit),
|
|
50
|
+
hasMore: rows.length > limit,
|
|
51
|
+
timezone: prepared.timezone,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** The `mail_counts` entity: what a command answers as its `item`. */
|
|
55
|
+
export const mailStatsItem = ({ by, received, sent, rows }) => ({
|
|
56
|
+
by,
|
|
57
|
+
received,
|
|
58
|
+
sent,
|
|
59
|
+
rows: rows.map(({ key, name, received, sent }) => ({ key, name, received, sent })),
|
|
60
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BudgetStop, RunBudget } from "../background/budget.js";
|
|
2
2
|
import type { LoadEngine } from "../readers/types.js";
|
|
3
3
|
import type { AccountKey, MessageStore } from "../store/index.js";
|
|
4
|
+
import type { MailAttachmentPolicy } from "./attachments.js";
|
|
4
5
|
import type { PartialOutcome } from "./partial.js";
|
|
5
6
|
import type { TagOutcome } from "./rule-tags.js";
|
|
6
7
|
import type { MailRule } from "./rules.js";
|
|
@@ -84,6 +85,8 @@ export interface MailSyncOptions {
|
|
|
84
85
|
max?: number;
|
|
85
86
|
maxBodyChars?: number;
|
|
86
87
|
maxAttachmentChars?: number;
|
|
88
|
+
/** Whether attachments are read into the index, and up to what size; `DEFAULT_ATTACHMENT_POLICY` when left out. */
|
|
89
|
+
attachments?: MailAttachmentPolicy;
|
|
87
90
|
now?: () => number;
|
|
88
91
|
/** Domain registration dates already known, for the sender score a rule asks for. */
|
|
89
92
|
ages?: ReadonlyMap<string, DomainAge>;
|
package/dist/mail/sync.js
CHANGED
|
@@ -3,7 +3,7 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
3
3
|
import { importEngine } from "../attachments/extract.js";
|
|
4
4
|
import { isBusy } from "../store/sqlite/transaction.js";
|
|
5
5
|
import { mailAccountKey } from "./account-key.js";
|
|
6
|
-
import { FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
6
|
+
import { DEFAULT_ATTACHMENT_POLICY, FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
7
7
|
import { BULK, bulkBox } from "./bulk.js";
|
|
8
8
|
import { parsedEmail } from "./email-input.js";
|
|
9
9
|
import { factsOf } from "./facts.js";
|
|
@@ -51,6 +51,7 @@ export const syncMail = async (source, store, options) => {
|
|
|
51
51
|
folders: () => mailFolders(options.account, source),
|
|
52
52
|
maxBodyChars: options.maxBodyChars ?? MAX_BODY_CHARS,
|
|
53
53
|
maxAttachmentChars: options.maxAttachmentChars ?? MAX_ATTACHMENT_CHARS,
|
|
54
|
+
policy: options.attachments ?? DEFAULT_ATTACHMENT_POLICY,
|
|
54
55
|
loadEngine: options.loadEngine ?? importEngine,
|
|
55
56
|
now: (options.now ?? Date.now)(),
|
|
56
57
|
...(options.budget ? { take: () => options.budget?.take() ?? null } : {}),
|
|
@@ -169,7 +170,7 @@ const syncRounds = async (source, store, options) => {
|
|
|
169
170
|
: {}),
|
|
170
171
|
};
|
|
171
172
|
};
|
|
172
|
-
const syncRound = async (source, store, { account, since, max = 200, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, now = Date.now, ages, loadEngine = importEngine, retryAttachments = false, rescan = false, saveCoverage = true, resumeFrom = RESUME_FROM, index, signal, }, plan) => {
|
|
173
|
+
const syncRound = async (source, store, { account, since, max = 200, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, attachments: policy = DEFAULT_ATTACHMENT_POLICY, now = Date.now, ages, loadEngine = importEngine, retryAttachments = false, rescan = false, saveCoverage = true, resumeFrom = RESUME_FROM, index, signal, }, plan) => {
|
|
173
174
|
if (retryAttachments)
|
|
174
175
|
return retryUnread({
|
|
175
176
|
store,
|
|
@@ -179,6 +180,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
179
180
|
max,
|
|
180
181
|
maxBodyChars,
|
|
181
182
|
maxAttachmentChars,
|
|
183
|
+
policy,
|
|
182
184
|
now,
|
|
183
185
|
loadEngine,
|
|
184
186
|
index,
|
|
@@ -759,7 +761,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
759
761
|
plan?.unreadable.delete(item.messageId);
|
|
760
762
|
const header = mailHeadersOf(raws[index]);
|
|
761
763
|
// A stop between attachments ends the chunk; what the earlier slices saved stays.
|
|
762
|
-
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, ...(signal ? { signal } : {}) }, attachments).catch(async (error) => {
|
|
764
|
+
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, policy, ...(signal ? { signal } : {}) }, attachments).catch(async (error) => {
|
|
763
765
|
await stopIfAborted();
|
|
764
766
|
throw error;
|
|
765
767
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LoadEngine } from "../readers/types.js";
|
|
2
|
+
import type { MessageStore } from "../store/index.js";
|
|
3
|
+
import type { MailSource } from "./source.js";
|
|
4
|
+
export interface WholeAttachment {
|
|
5
|
+
position: number;
|
|
6
|
+
name: string;
|
|
7
|
+
mime: string | null;
|
|
8
|
+
/** `null` when the email held no content for it. */
|
|
9
|
+
bytes: Uint8Array | null;
|
|
10
|
+
status: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reads one stored email from its source again and every attachment of it, whatever its size: what a person asks for
|
|
14
|
+
* by name is read even when a sync's policy left it. The new text is saved as a sync saves it, so the search index
|
|
15
|
+
* takes it; the files are only answered, never written. Gmail's email is found by its id in `folders`; elsewhere it is
|
|
16
|
+
* read at its newest stored place, trusted only when `identify` gives the stored id for the bytes read there.
|
|
17
|
+
*/
|
|
18
|
+
export declare const readEmailWhole: ({ store, source, address, externalId, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, }: {
|
|
19
|
+
store: MessageStore;
|
|
20
|
+
source: MailSource;
|
|
21
|
+
address: string;
|
|
22
|
+
externalId: string;
|
|
23
|
+
folders: () => Promise<readonly string[]>;
|
|
24
|
+
maxBodyChars?: number;
|
|
25
|
+
maxAttachmentChars?: number;
|
|
26
|
+
loadEngine?: LoadEngine;
|
|
27
|
+
now?: number;
|
|
28
|
+
}) => Promise<WholeAttachment[]>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { importEngine } from "../attachments/extract.js";
|
|
3
|
+
import { mailAccountKey } from "./account-key.js";
|
|
4
|
+
import { MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
5
|
+
import { parsedEmail } from "./email-input.js";
|
|
6
|
+
import { mailHeadersOf } from "./headers.js";
|
|
7
|
+
import { MAX_BODY_CHARS, parseMail } from "./parse.js";
|
|
8
|
+
import { PARTIAL } from "./partial.js";
|
|
9
|
+
/**
|
|
10
|
+
* Reads one stored email from its source again and every attachment of it, whatever its size: what a person asks for
|
|
11
|
+
* by name is read even when a sync's policy left it. The new text is saved as a sync saves it, so the search index
|
|
12
|
+
* takes it; the files are only answered, never written. Gmail's email is found by its id in `folders`; elsewhere it is
|
|
13
|
+
* read at its newest stored place, trusted only when `identify` gives the stored id for the bytes read there.
|
|
14
|
+
*/
|
|
15
|
+
export const readEmailWhole = async ({ store, source, address, externalId, folders, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, loadEngine = importEngine, now = Date.now(), }) => {
|
|
16
|
+
const { id: accountId } = await store.accounts.storedAccount(mailAccountKey(address));
|
|
17
|
+
const stored = await store.mail.emailPlace(accountId, externalId);
|
|
18
|
+
if (stored === null)
|
|
19
|
+
throw new CliError("not_found", `${externalId} is not stored for ${address}`);
|
|
20
|
+
let item;
|
|
21
|
+
if (source.find)
|
|
22
|
+
for (const folder of await folders()) {
|
|
23
|
+
item = (await source.find([externalId], folder)).find(({ messageId }) => messageId === externalId);
|
|
24
|
+
if (item)
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const place = stored.locations[0];
|
|
29
|
+
if (place)
|
|
30
|
+
item = {
|
|
31
|
+
uid: String(place.uid),
|
|
32
|
+
folder: place.folder,
|
|
33
|
+
validity: place.uidvalidity,
|
|
34
|
+
receivedAt: new Date(stored.receivedAt ?? 0).toISOString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (item === undefined)
|
|
38
|
+
throw new CliError("not_found", `the mail server no longer gives ${externalId}`);
|
|
39
|
+
const [raw] = await source.fetchRaw([item]);
|
|
40
|
+
if (!(raw instanceof Uint8Array))
|
|
41
|
+
throw new CliError("provider_unavailable", `${externalId} could not be read from the mail server`);
|
|
42
|
+
if (!source.find && source.identify?.(raw, item).messageId !== externalId)
|
|
43
|
+
throw new CliError("not_found", `the mail server holds another email where ${externalId} was`);
|
|
44
|
+
const mail = await parseMail(raw, maxBodyChars);
|
|
45
|
+
const log = [];
|
|
46
|
+
const files = await readAttachments(mail, externalId, { loadEngine, maxAttachmentChars, at: now }, log);
|
|
47
|
+
const { [PARTIAL]: _partial, ...metadata } = stored.metadata ?? {};
|
|
48
|
+
const whole = { ...item, messageId: externalId, threadId: stored.threadExternalId };
|
|
49
|
+
await store.mail.saveThread({
|
|
50
|
+
accountId,
|
|
51
|
+
externalId: stored.threadExternalId,
|
|
52
|
+
now,
|
|
53
|
+
emails: [
|
|
54
|
+
{
|
|
55
|
+
...parsedEmail(mail, whole, mailHeadersOf(raw), address),
|
|
56
|
+
receivedAt: stored.receivedAt,
|
|
57
|
+
size: raw.byteLength,
|
|
58
|
+
metadata,
|
|
59
|
+
attachments: files,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
return mail.attachments.map((part, position) => ({
|
|
64
|
+
position,
|
|
65
|
+
name: part.name,
|
|
66
|
+
mime: part.mime,
|
|
67
|
+
bytes: part.bytes,
|
|
68
|
+
status: log[position]?.status ?? "unavailable",
|
|
69
|
+
}));
|
|
70
|
+
};
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { type MeetingProjectEvidenceAnswer, type MeetingProjectEvidenceItem, mee
|
|
|
32
32
|
export { MEETING_PROJECT_EVIDENCE_QUERY, type MeetingProjectEvidenceRequest, prepareMeetingProjectEvidence, } from "./project-evidence-query.js";
|
|
33
33
|
export { type MeetingProviderScope, snapshotMeetingScope } from "./provider.js";
|
|
34
34
|
export { type PullOptions, pull, type RunReport } from "./pull.js";
|
|
35
|
-
export { event, eventItem, meetingLink, meetingLinkItem, meetingParticipants, meetingPassage, meetingReview, meetingReviewItem, meetingSpeaker, meetingSpeakerItem, meetingStats, meetingStatsItem, } from "./read-answers.js";
|
|
35
|
+
export { event, eventItem, meetingCounts, meetingCountsItem, meetingLink, meetingLinkItem, meetingParticipants, meetingPassage, meetingReview, meetingReviewItem, meetingSpeaker, meetingSpeakerItem, meetingStats, meetingStatsItem, } from "./read-answers.js";
|
|
36
36
|
export type { MeetingContextReadCapabilities, MeetingReadBudget, MeetingReadCapabilities, MeetingReadScope, TranscriptContextInput, TranscriptContextPage, TranscriptPage, TranscriptReadScope, TranscriptRowPage, } from "./read-store.js";
|
|
37
37
|
export { listed, MeetingError, meetingDetailsAnswer, meetingPeople, meetingSummaryAnswer, meetingTranscriptAnswer, type TranscriptReadOptions, } from "./reads.js";
|
|
38
38
|
export { installRecordingArchiveSchedule, parseRecordingArchiveSchedule, planRecordingArchiveSchedule, type RecordingArchiveSchedule, type RecordingArchiveScheduleInput, type RecordingArchiveScheduler, recordingArchiveScheduleWindow, } from "./recording-schedule.js";
|
|
@@ -51,7 +51,7 @@ export { createMeetingSeriesInventoryReader } from "./series-inventory.js";
|
|
|
51
51
|
export type { MeetingSeriesCandidate, MeetingSeriesInventoryPage, MeetingSeriesInventoryPort, MeetingSeriesInventoryRead, MeetingSeriesInventoryRequest, MeetingSeriesInventoryResult, } from "./series-inventory-types.js";
|
|
52
52
|
export { validateMeetingSeriesInventoryCursor } from "./series-inventory-validation.js";
|
|
53
53
|
export type { MeetingSource, SourceParticipant } from "./source.js";
|
|
54
|
-
export { aggregateMeetingStats, type MeetingSpeakerStats, type MeetingStats, type MeetingStatsInput, type MeetingStatsRow, type MeetingStatsSelection, type MeetingStatsStore, type MeetingWeekStats, } from "./stats.js";
|
|
54
|
+
export { aggregateMeetingCounts, aggregateMeetingStats, MEETING_COUNTS_GROUPINGS, type MeetingCounts, type MeetingCountsGrouping, type MeetingCountsInput, type MeetingCountsMeeting, type MeetingCountsRow, type MeetingSpeakerStats, type MeetingStats, type MeetingStatsInput, type MeetingStatsRow, type MeetingStatsSelection, type MeetingStatsStore, type MeetingWeekStats, NO_SERIES, } from "./stats.js";
|
|
55
55
|
export type { ChatInput, ContractCase, EventCandidateFilter, IdentityInput, MeetingDetails, MeetingFilter, MeetingHitSource, MeetingInput, MeetingSave, MeetingStore, NewRecord, ParticipantInput, SearchHit, TranscriptInput, TranscriptLanguageFill, TranscriptRowInput, } from "./store.js";
|
|
56
56
|
export { meetingTimestamp } from "./timestamp.js";
|
|
57
57
|
export type { MeetingTranscriptReceiptStore, MeetingTranscriptStore, TranscriptAppend, TranscriptAppendReceipt, UnlinkedTranscriptInput, } from "./transcript-store.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { meetingProjectEvidence, meetingProjectEvidenceAnswer, } from "./project
|
|
|
24
24
|
export { MEETING_PROJECT_EVIDENCE_QUERY, prepareMeetingProjectEvidence, } from "./project-evidence-query.js";
|
|
25
25
|
export { snapshotMeetingScope } from "./provider.js";
|
|
26
26
|
export { pull } from "./pull.js";
|
|
27
|
-
export { event, eventItem, meetingLink, meetingLinkItem, meetingParticipants, meetingPassage, meetingReview, meetingReviewItem, meetingSpeaker, meetingSpeakerItem, meetingStats, meetingStatsItem, } from "./read-answers.js";
|
|
27
|
+
export { event, eventItem, meetingCounts, meetingCountsItem, meetingLink, meetingLinkItem, meetingParticipants, meetingPassage, meetingReview, meetingReviewItem, meetingSpeaker, meetingSpeakerItem, meetingStats, meetingStatsItem, } from "./read-answers.js";
|
|
28
28
|
export { listed, MeetingError, meetingDetailsAnswer, meetingPeople, meetingSummaryAnswer, meetingTranscriptAnswer, } from "./reads.js";
|
|
29
29
|
export { installRecordingArchiveSchedule, parseRecordingArchiveSchedule, planRecordingArchiveSchedule, recordingArchiveScheduleWindow, } from "./recording-schedule.js";
|
|
30
30
|
export { parseRecordingSyncCheckpoint, RecordingSyncError, recordingAssetFingerprint, recordingWindows, syncRecordingArchive, } from "./recording-sync.js";
|
|
@@ -38,7 +38,7 @@ export { validateMeetingHitSearch } from "./search-validation.js";
|
|
|
38
38
|
export { validateMeetingSeriesHistory, } from "./series-history.js";
|
|
39
39
|
export { createMeetingSeriesInventoryReader } from "./series-inventory.js";
|
|
40
40
|
export { validateMeetingSeriesInventoryCursor } from "./series-inventory-validation.js";
|
|
41
|
-
export { aggregateMeetingStats, } from "./stats.js";
|
|
41
|
+
export { aggregateMeetingCounts, aggregateMeetingStats, MEETING_COUNTS_GROUPINGS, NO_SERIES, } from "./stats.js";
|
|
42
42
|
export { meetingTimestamp } from "./timestamp.js";
|
|
43
43
|
export { parseTranscriptText, TranscriptTextError } from "./transcript-text.js";
|
|
44
44
|
export { transcribeMeeting, } from "./transcription.js";
|