@wirecat/cli-core 0.563.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/calendar/mcp/index.d.ts +12 -12
  3. package/dist/cli/messenger/archive-commands.js +17 -0
  4. package/dist/cli/messenger/catch-up-answers.d.ts +5 -0
  5. package/dist/cli/messenger/catch-up-answers.js +3 -0
  6. package/dist/cli/messenger/search-answers.d.ts +1 -0
  7. package/dist/cli/messenger/search-answers.js +1 -1
  8. package/dist/cli/messenger/stats-answers.d.ts +3 -3
  9. package/dist/cli/messenger/stats-answers.js +21 -9
  10. package/dist/cli/messenger/tasks-command.js +35 -14
  11. package/dist/conversations/mail-text.d.ts +6 -0
  12. package/dist/conversations/mail-text.js +73 -0
  13. package/dist/domain/calendar.d.ts +2 -0
  14. package/dist/domain/calendar.js +6 -0
  15. package/dist/entities/chat-stats.d.ts +16 -2
  16. package/dist/entities/chat-stats.js +18 -3
  17. package/dist/entities/index.d.ts +50 -802
  18. package/dist/entities/index.js +23 -16
  19. package/dist/entities/mail-counts.d.ts +15 -0
  20. package/dist/entities/mail-counts.js +16 -0
  21. package/dist/entities/meeting.d.ts +1 -1
  22. package/dist/entities/meeting.js +1 -1
  23. package/dist/entities/store-records.d.ts +8 -0
  24. package/dist/entities/store-records.js +8 -0
  25. package/dist/errors/reasons.js +5 -0
  26. package/dist/mail/cli/index.d.ts +5 -0
  27. package/dist/mail/cli/index.js +27 -3
  28. package/dist/mail/cli/review.js +11 -4
  29. package/dist/mail/index.d.ts +2 -0
  30. package/dist/mail/index.js +2 -0
  31. package/dist/mail/stats.d.ts +50 -0
  32. package/dist/mail/stats.js +60 -0
  33. package/dist/mail/whole.d.ts +4 -4
  34. package/dist/mail/whole.js +4 -3
  35. package/dist/meetings/index.d.ts +2 -2
  36. package/dist/meetings/index.js +2 -2
  37. package/dist/meetings/mcp/index.d.ts +15 -15
  38. package/dist/meetings/read-answers.d.ts +22 -1
  39. package/dist/meetings/read-answers.js +38 -0
  40. package/dist/meetings/stats.d.ts +44 -0
  41. package/dist/meetings/stats.js +77 -7
  42. package/dist/search/lucene/registry.js +2 -1
  43. package/dist/search/question-plan.d.ts +16 -0
  44. package/dist/search/question-plan.js +34 -0
  45. package/dist/services/events-search.d.ts +5 -1
  46. package/dist/services/events-search.js +10 -4
  47. package/dist/services/index.d.ts +1 -1
  48. package/dist/services/index.js +1 -1
  49. package/dist/services/meeting-stats.d.ts +12 -1
  50. package/dist/services/meeting-stats.js +12 -0
  51. package/dist/services/messages-discovery.d.ts +1 -5
  52. package/dist/services/messages-discovery.js +5 -18
  53. package/dist/services/messages-meaning.js +25 -3
  54. package/dist/services/messages.js +4 -3
  55. package/dist/services/notes-search.d.ts +5 -1
  56. package/dist/services/notes-search.js +16 -5
  57. package/dist/services/proposals/ids.d.ts +2 -2
  58. package/dist/services/proposals/ids.js +5 -3
  59. package/dist/services/proposals/kinds/findings.d.ts +1 -1
  60. package/dist/services/proposals/kinds/findings.js +1 -1
  61. package/dist/services/proposals/kinds/meeting-findings.d.ts +7 -0
  62. package/dist/services/proposals/kinds/meeting-findings.js +73 -0
  63. package/dist/services/proposals/kinds/task-close.d.ts +7 -0
  64. package/dist/services/proposals/kinds/task-close.js +76 -0
  65. package/dist/services/proposals/registry.js +4 -0
  66. package/dist/services/search-all.d.ts +5 -0
  67. package/dist/services/search-all.js +47 -14
  68. package/dist/services/task-stats.d.ts +41 -0
  69. package/dist/services/task-stats.js +126 -0
  70. package/dist/services/tasks.d.ts +17 -4
  71. package/dist/services/tasks.js +31 -14
  72. package/dist/store/chats.d.ts +3 -1
  73. package/dist/store/chats.js +6 -0
  74. package/dist/store/decisions.d.ts +4 -2
  75. package/dist/store/decisions.js +5 -3
  76. package/dist/store/exports.d.ts +1 -1
  77. package/dist/store/exports.js +1 -1
  78. package/dist/store/index.d.ts +2 -1
  79. package/dist/store/index.js +1 -1
  80. package/dist/store/sqlite/completeness.d.ts +17 -0
  81. package/dist/store/sqlite/completeness.js +49 -13
  82. package/dist/store/sqlite/lucene-fields.js +2 -0
  83. package/dist/store/sqlite/lucene-groups.d.ts +15 -0
  84. package/dist/store/sqlite/lucene-groups.js +21 -0
  85. package/dist/store/sqlite/lucene.d.ts +2 -9
  86. package/dist/store/sqlite/lucene.js +1 -12
  87. package/dist/store/sqlite/meeting-stats.d.ts +2 -0
  88. package/dist/store/sqlite/meeting-stats.js +27 -1
  89. package/dist/store/sqlite/retention.js +2 -7
  90. package/dist/store/sqlite/task-board.js +2 -0
  91. package/dist/store/sqlite/task-stats.d.ts +32 -0
  92. package/dist/store/sqlite/task-stats.js +45 -0
  93. package/dist/store/sqlite/tasks.d.ts +2 -1
  94. package/dist/store/sqlite/tasks.js +2 -0
  95. package/package.json +1 -1
  96. package/parity.json +13 -1
@@ -44,9 +44,10 @@ import { joinRequest } from "./join-request.js";
44
44
  import { organization, project, reminder } from "./knowledge-lists.js";
45
45
  import { botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, callRecord, scheduledJob, stickerInventory, topicIcon, } from "./list-results.js";
46
46
  import { messageEvent, serveSummary, watchedMessage, watchItem } from "./listener.js";
47
+ import { mailCounts } from "./mail-counts.js";
47
48
  import { mailFindings } from "./mail-findings.js";
48
49
  import { mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer } from "./media-archive.js";
49
- import { event, meeting, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "./meeting.js";
50
+ import { event, meeting, meetingCounts, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "./meeting.js";
50
51
  import { meetingEvidence, meetingPersonContext } from "./meeting-evidence.js";
51
52
  import { meetingFindings } from "./meeting-findings.js";
52
53
  import { meetingPreparation } from "./meeting-preparation.js";
@@ -83,7 +84,7 @@ import { retention } from "./retention.js";
83
84
  import { savedSearch, searchHistoryClear } from "./saved-search.js";
84
85
  import { sendAttempt } from "./send-attempt.js";
85
86
  import { storeBackup, storeCheck, storeCopyRemoval, storeDecrypt, storeInfo, storeMigration, storeReindex, storeRepair, storeReset, storeRestore, } from "./store-maintenance.js";
86
- import { storeChatStatus, storeClear, storeExport, storeGapPlan, storeGapRepair, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, syncProgress, } from "./store-records.js";
87
+ import { chatCoverage, storeChatStatus, storeClear, storeExport, storeGapPlan, storeGapRepair, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, syncProgress, } from "./store-records.js";
87
88
  import { storeTable } from "./store-table.js";
88
89
  import { automaticTags, tagChange } from "./tag-change.js";
89
90
  import { tagging } from "./tagging.js";
@@ -96,7 +97,7 @@ import { topic } from "./topic.js";
96
97
  * subset of its fields, never a renamed one. Commands move onto these one by one; until then, a schema
97
98
  * is the target, not a description of today's output.
98
99
  */
99
- export { account, accountChange, accountPrivacy, accountPrivacyChange, accountProfile, accountSession, adminReport, agenda, analysisConsent, analysisConsentRevocation, attachment, attachmentBytes, attachmentExtraction, attachmentTextChange, automaticTags, botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, cachedChatMetadata, callRecord, chart, chat, chatAdminChange, chatArchive, chatEvent, chatFolderChange, chatFolderOrder, chatFolderRemoval, chatGroupChange, chatInspection, chatInvite, chatInviteChange, chatMembersChange, chatMembersFetch, chatMetadata, chatMetadataRefresh, chatModeration, chatMuting, chatPin, chatRead, chatRemoval, chatRequestChange, chatRules, chatSender, chatStats, chatTracking, chatUnread, contact, contactActivity, contactAlias, contactCheck, contactContext, contactImport, contactLookup, contactNote, contactNoteRemoval, contactPreparation, contactStateChange, contactSync, contactUpdate, conversation, conversationBatch, conversationBatchStatus, conversationBuildResult, conversationChange, conversationDetails, conversationEmbedding, conversationEmbeddingStatus, conversationHit, conversationPreparation, conversationReadiness, counterRefresh, counters, document, documentLink, endedAccountSessions, evidence, evidencePacket, fact, folder, hit, identity, imageEmbedding, inviteLink, job, joinRequest, linkedPerson, mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer, meeting, meetingProjectCandidates, meetingSeriesInventory, member, memory, mention, mentionDecision, mentionDetails, mentionDomain, mentionRebuild, message, messageContext, messageCounts, messageDeletion, messageDownload, messageEvent, messageLink, messageLinks, messagePin, messageStar, messageSync, messageTranscript, messageWrite, model, modelKey, note, officialStats, organization, person, place, poll, pollChange, pollVote, project, ranking, reaction, reactionChange, recipient, recipientAdded, recipientChange, recipientOff, recipientRemoved, recordedRun, reference, relationship, reminder, removedModelKey, responseTimes, retention, runDetails, runHit, runLocation, savedSearch, scheduledJob, searchHistoryClear, sendAttempt, serveSummary, stickerInventory, storeBackup, storeChatStatus, storeCheck, storeClear, storeCopyRemoval, storeDecrypt, storedModelKey, storeExport, storeGapPlan, storeGapRepair, storeInfo, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, storeMigration, storeReindex, storeRepair, storeReset, storeRestore, storeTable, tagChange, tagging, task, taskAnalysis, taskAnalysisMark, taskCard, taskCategories, taskClosed, taskCreated, taskLinks, taskMerge, taskPair, taskScan, taskSimilar, taskStats, taskUnmerge, topic, topicIcon, trackedChat, trackedChatHistory, waitingChats, watchedMessage, watchItem, };
100
+ export { account, accountChange, accountPrivacy, accountPrivacyChange, accountProfile, accountSession, adminReport, agenda, analysisConsent, analysisConsentRevocation, attachment, attachmentBytes, attachmentExtraction, attachmentTextChange, automaticTags, botAdmin, botMenuEntry, botProfile, botSeenChat, botTemplate, botWebhook, cachedChatMetadata, callRecord, chart, chat, chatAdminChange, chatArchive, chatCoverage, chatEvent, chatFolderChange, chatFolderOrder, chatFolderRemoval, chatGroupChange, chatInspection, chatInvite, chatInviteChange, chatMembersChange, chatMembersFetch, chatMetadata, chatMetadataRefresh, chatModeration, chatMuting, chatPin, chatRead, chatRemoval, chatRequestChange, chatRules, chatSender, chatStats, chatTracking, chatUnread, contact, contactActivity, contactAlias, contactCheck, contactContext, contactImport, contactLookup, contactNote, contactNoteRemoval, contactPreparation, contactStateChange, contactSync, contactUpdate, conversation, conversationBatch, conversationBatchStatus, conversationBuildResult, conversationChange, conversationDetails, conversationEmbedding, conversationEmbeddingStatus, conversationHit, conversationPreparation, conversationReadiness, counterRefresh, counters, document, documentLink, endedAccountSessions, evidence, evidencePacket, fact, folder, hit, identity, imageEmbedding, inviteLink, job, joinRequest, linkedPerson, mailCounts, mediaArchive, mediaArchiveCheck, mediaArchiveMove, mediaArchiveTimer, meeting, meetingProjectCandidates, meetingSeriesInventory, member, memory, mention, mentionDecision, mentionDetails, mentionDomain, mentionRebuild, message, messageContext, messageCounts, messageDeletion, messageDownload, messageEvent, messageLink, messageLinks, messagePin, messageStar, messageSync, messageTranscript, messageWrite, model, modelKey, note, officialStats, organization, person, place, poll, pollChange, pollVote, project, ranking, reaction, reactionChange, recipient, recipientAdded, recipientChange, recipientOff, recipientRemoved, recordedRun, reference, relationship, reminder, removedModelKey, responseTimes, retention, runDetails, runHit, runLocation, savedSearch, scheduledJob, searchHistoryClear, sendAttempt, serveSummary, stickerInventory, storeBackup, storeChatStatus, storeCheck, storeClear, storeCopyRemoval, storeDecrypt, storedModelKey, storeExport, storeGapPlan, storeGapRepair, storeInfo, storeJobCancel, storeJobClear, storeJobDetails, storeJobRetry, storeMigration, storeReindex, storeRepair, storeReset, storeRestore, storeTable, tagChange, tagging, task, taskAnalysis, taskAnalysisMark, taskCard, taskCategories, taskClosed, taskCreated, taskLinks, taskMerge, taskPair, taskScan, taskSimilar, taskStats, taskUnmerge, topic, topicIcon, trackedChat, trackedChatHistory, waitingChats, watchedMessage, watchItem, };
100
101
  export const ENTITIES = {
101
102
  sync_progress: syncProgress,
102
103
  ...MEETING_RESULT_ENTITIES,
@@ -126,19 +127,22 @@ export const ENTITIES = {
126
127
  attachments: v.optional(v.array(hitFile)),
127
128
  untrusted,
128
129
  }),
129
- "message.summary": v.pick(message, [
130
- "ref",
131
- "chat_id",
132
- "chat_title",
133
- "timestamp",
134
- "sender_name",
135
- "sender_is_me",
136
- "outgoing",
137
- "text",
138
- "transcript",
139
- "attachments",
140
- "email",
141
- ]),
130
+ "message.summary": v.strictObject({
131
+ ...v.pick(message, [
132
+ "ref",
133
+ "chat_id",
134
+ "chat_title",
135
+ "timestamp",
136
+ "sender_name",
137
+ "sender_is_me",
138
+ "outgoing",
139
+ "text",
140
+ "transcript",
141
+ "attachments",
142
+ "email",
143
+ ]).entries,
144
+ untrusted,
145
+ }),
142
146
  message_link: messageLink,
143
147
  message_write: messageWrite,
144
148
  message_send: messageSend,
@@ -257,6 +261,7 @@ export const ENTITIES = {
257
261
  meeting_review: meetingReview,
258
262
  meeting_link: meetingLink,
259
263
  meeting_stats: meetingStats,
264
+ meeting_counts: meetingCounts,
260
265
  meeting_speaker: meetingSpeaker,
261
266
  meeting_participants: meetingParticipants,
262
267
  meeting_passage: meetingPassage,
@@ -294,6 +299,7 @@ export const ENTITIES = {
294
299
  job,
295
300
  store_chat_status: storeChatStatus,
296
301
  store_clear: storeClear,
302
+ chat_coverage: chatCoverage,
297
303
  store_export: storeExport,
298
304
  store_gap_plan: storeGapPlan,
299
305
  store_gap_repair: storeGapRepair,
@@ -351,6 +357,7 @@ export const ENTITIES = {
351
357
  message_counts: messageCounts,
352
358
  response_times: responseTimes,
353
359
  waiting_chats: waitingChats,
360
+ mail_counts: mailCounts,
354
361
  ranking,
355
362
  admin_report: adminReport,
356
363
  analysis_consent: analysisConsent,
@@ -0,0 +1,15 @@
1
+ import * as v from "valibot";
2
+ /** Stored mail received and sent, one row per calendar day, week (by its Monday) or sender. */
3
+ export declare const mailCounts: v.StrictObjectSchema<{
4
+ readonly by: v.PicklistSchema<["day", "week", "sender"], undefined>;
5
+ readonly received: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
6
+ readonly sent: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
7
+ readonly rows: v.ArraySchema<v.StrictObjectSchema<{
8
+ /** `YYYY-MM-DD`, or the sender's address. */
9
+ readonly key: v.StringSchema<undefined>;
10
+ readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
11
+ readonly received: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
12
+ /** Always 0 by sender: the owner sends that mail. */
13
+ readonly sent: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
14
+ }, undefined>, undefined>;
15
+ }, undefined>;
@@ -0,0 +1,16 @@
1
+ import * as v from "valibot";
2
+ import { count, text } from "./fields.js";
3
+ /** Stored mail received and sent, one row per calendar day, week (by its Monday) or sender. */
4
+ export const mailCounts = v.strictObject({
5
+ by: v.picklist(["day", "week", "sender"]),
6
+ received: count,
7
+ sent: count,
8
+ rows: v.array(v.strictObject({
9
+ /** `YYYY-MM-DD`, or the sender's address. */
10
+ key: text,
11
+ name: v.nullable(text),
12
+ received: count,
13
+ /** Always 0 by sender: the owner sends that mail. */
14
+ sent: count,
15
+ })),
16
+ });
@@ -1 +1 @@
1
- export { event, meeting, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "../meetings/index.js";
1
+ export { event, meeting, meetingCounts, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "../meetings/index.js";
@@ -1 +1 @@
1
- export { event, meeting, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "../meetings/index.js";
1
+ export { event, meeting, meetingCounts, meetingDetails, meetingHit, meetingLink, meetingParticipants, meetingPassage, meetingProjectEvidence, meetingReview, meetingSpeaker, meetingStats, meetingSummary, meetingTranscript, } from "../meetings/index.js";
@@ -12,6 +12,14 @@ export declare const storeChatStatus: v.StrictObjectSchema<{
12
12
  readonly to: v.NumberSchema<undefined>;
13
13
  }, undefined>, undefined>;
14
14
  }, undefined>;
15
+ /** `store chats status`: chats = fetched + partial + never, over the account's searchable chats of every kind. */
16
+ export declare const chatCoverage: v.StrictObjectSchema<{
17
+ readonly chats: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
18
+ readonly fetched: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
19
+ readonly partial: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
20
+ readonly never: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
21
+ readonly oldest_message_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
22
+ }, undefined>;
15
23
  export declare const storeClear: v.StrictObjectSchema<{
16
24
  readonly cleared: v.BooleanSchema<undefined>;
17
25
  readonly dry_run: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
@@ -17,6 +17,14 @@ export const storeChatStatus = v.strictObject({
17
17
  last_stored_at: v.nullable(time),
18
18
  held: v.array(range),
19
19
  });
20
+ /** `store chats status`: chats = fetched + partial + never, over the account's searchable chats of every kind. */
21
+ export const chatCoverage = v.strictObject({
22
+ chats: count,
23
+ fetched: count,
24
+ partial: count,
25
+ never: count,
26
+ oldest_message_at: v.nullable(time),
27
+ });
20
28
  export const storeClear = v.strictObject({
21
29
  cleared: v.boolean(),
22
30
  dry_run: v.optional(v.literal(true)),
@@ -55,6 +55,11 @@ const OTHERS = [
55
55
  validation("file.locked", "another process holds the file's lock"),
56
56
  ["mail.source-failed", "provider_error", "the mail server refused or did not complete a request"],
57
57
  ["mail.search-refused", "provider_error", "the mail server refused to search a folder"],
58
+ [
59
+ "mail.attachments-unread",
60
+ "provider_unavailable",
61
+ "the email could not be read again, so its skipped attachments have no text",
62
+ ],
58
63
  ["mirror.partial", "provider_error", "some mirrored files were not synced"],
59
64
  ["profile.wrong-account", "authentication_error", "the signed-in source account does not match this profile"],
60
65
  validation("push.too-many-files", "the folder holds more files than one push takes"),
@@ -32,6 +32,11 @@ export interface MailReads {
32
32
  mount?: MailMount;
33
33
  /** How `--server-time` help states its default when a provider's own differs (`MailServerAccess.timeMs`). */
34
34
  serverTimeDefault?: string;
35
+ /**
36
+ * Reads one stored email again from the mailbox's server, every attachment whatever its size (`readEmailWhole`).
37
+ * With it, `messages show` reads an email whose attachment a sync's policy left unread before it answers.
38
+ */
39
+ whole?: (command: Command, store: MessageStore, mailbox: AccountKey, emailId: string) => Promise<void>;
35
40
  }
36
41
  export declare const THREAD_QUERY: {
37
42
  readonly entity: "mail_thread";
@@ -5,7 +5,7 @@ import { Command, InvalidArgumentError } from "commander";
5
5
  import * as v from "valibot";
6
6
  import { answerEntity } from "../../cli/answer-entity.js";
7
7
  import { environmentOf, outputFor } from "../../cli/context.js";
8
- import { LOCAL_READ, REMOTE_READ } from "../../cli/effects.js";
8
+ import { FETCH, LOCAL_READ, REMOTE_READ } from "../../cli/effects.js";
9
9
  import { itemOf, listOf, renderAnswer, snakeKeys } from "../../cli/envelope.js";
10
10
  import { listQuery, typedQuery, withQuery } from "../../cli/list-query.js";
11
11
  import { messagesAnswer, offsetOf, shownOf } from "../../cli/messenger/search-answers.js";
@@ -13,6 +13,7 @@ import { backendRequest, noteServer, serverSearchOptions } from "../../cli/messe
13
13
  import { positiveCount } from "../../cli/paging.js";
14
14
  import { formatReference, parseReference } from "../../domain/references.js";
15
15
  import { count, reference, text, time, untrusted } from "../../entities/fields.js";
16
+ import { POLICY_SKIPS } from "../../mail/attachments.js";
16
17
  import { attachmentsText, hitText, searchMail, showMail, withAttachmentText } from "../../mail/read.js";
17
18
  import { searchMailServer } from "../../mail/server-search.js";
18
19
  import { atDetail } from "../../output-detail.js";
@@ -28,6 +29,14 @@ const absentAccount = (error) => {
28
29
  return undefined;
29
30
  throw error;
30
31
  };
32
+ /** The shown email whose attachment a sync's policy left without text, if any. */
33
+ const policySkipped = (emails) => {
34
+ const shown = emails.find((email) => email.anchor) ?? emails[0];
35
+ const skips = POLICY_SKIPS;
36
+ return shown?.attachments?.some((file) => skips.includes(String(file.extraction)))
37
+ ? shown
38
+ : undefined;
39
+ };
31
40
  const DEFAULT_LIMIT = 20;
32
41
  const MAX_LIMIT = 100;
33
42
  const limitOf = (value) => {
@@ -203,7 +212,7 @@ const shownText = (emails) => `${emails.map((email) => [hitText(email, "full"),
203
212
  const messagesCommand = (mail) => {
204
213
  const messages = new Command("messages").summary("read stored emails").description("read the emails sync stored");
205
214
  annotate(messages.command("show"), {
206
- effects: LOCAL_READ,
215
+ effects: mail.whole ? FETCH : LOCAL_READ,
207
216
  examples: [
208
217
  `${mail.app.command} personal messages show 'msg:email/me%40example.com/thread-1/%3Cid%40example.com%3E'`,
209
218
  `${mail.app.command} personal messages show 'msg:email/me%40example.com/thread-1/%3Cid%40example.com%3E' --before-n 2`,
@@ -218,15 +227,30 @@ const messagesCommand = (mail) => {
218
227
  .action(async function (reference) {
219
228
  const { beforeN, afterN } = this.opts();
220
229
  const full = detailOf(this) === "full";
230
+ let unread;
221
231
  const emails = await mail.read(this, async (store, mailbox) => {
222
232
  threadOf(mailbox, reference);
233
+ const shown = await showMail(store, reference, { before: beforeN, after: afterN, full });
234
+ const skipped = mail.whole ? policySkipped(shown) : undefined;
235
+ if (!skipped || !mail.whole)
236
+ return shown;
237
+ try {
238
+ await mail.whole(this, store, mailbox, skipped.id);
239
+ }
240
+ catch (error) {
241
+ if (!(error instanceof CliError))
242
+ throw error;
243
+ // The stored email is still worth showing; the answer says its attachments were not read.
244
+ unread = { code: error.code, reason: "mail.attachments-unread", source: "server" };
245
+ return shown;
246
+ }
223
247
  return showMail(store, reference, { before: beforeN, after: afterN, full });
224
248
  });
225
249
  const output = outputFor(this);
226
250
  const shown = emails.map((email) => snakeKeys(atDetail(email)));
227
251
  renderAnswer(this, output, beforeN || afterN
228
252
  ? listOf("messages show", { items: shown, limit: Math.max(1, shown.length), has_more: false })
229
- : itemOf(shown.find((_, at) => emails[at]?.anchor) ?? shown[0]), () => shownText(emails));
253
+ : itemOf(shown.find((_, at) => emails[at]?.anchor) ?? shown[0], unread ? { meta: { complete: false, issues: [unread] } } : undefined), () => shownText(emails));
230
254
  });
231
255
  messages.addCommand(acknowledgeCommand(mail));
232
256
  return messages;
@@ -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",
@@ -132,6 +135,7 @@ const reviewAnswer = (found, mailbox, fresh, step) => {
132
135
  }
133
136
  : {}),
134
137
  },
138
+ untrusted: REVIEW_UNTRUSTED,
135
139
  })),
136
140
  limit: Math.max(1, read.length),
137
141
  has_more: false,
@@ -152,8 +156,10 @@ const holderReview = async (command, mail, holder, query) => {
152
156
  : new Map();
153
157
  return { read, mailbox, marks };
154
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) }));
155
161
  const chats = new Map();
156
- for (const email of read.emails) {
162
+ for (const email of emails) {
157
163
  const chat = chats.get(email.chatId) ?? {
158
164
  id: email.chatId,
159
165
  title: email.chatTitle,
@@ -191,7 +197,7 @@ const holderReview = async (command, mail, holder, query) => {
191
197
  output.renderer.note(read.mark
192
198
  ? `${holder}: past ${read.mark.throughId}; acknowledge with --cursor ${read.cursor}${read.more ? "; more after these" : ""}`
193
199
  : `${holder} has no mark yet: it starts from now — acknowledge with --cursor ${read.cursor}`);
194
- return read.emails.map((hit) => `${hitText(hit)}${markText(marks.get(hit.id))}\n`).join("");
200
+ return emails.map((hit) => `${hitText(hit)}${markText(marks.get(hit.id))}\n`).join("");
195
201
  });
196
202
  };
197
203
  /**
@@ -217,7 +223,8 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
217
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")
218
224
  .option("--waiting [duration]", "only threads where you wrote last and nobody answered for this long — 3d; 7d if not given")
219
225
  .option("--holder <name>", "read for this holder — an agent or a pipeline — what is stored past its processing mark, by store order; " +
220
- "bulk, envelopes, drafts, suspicious senders and partial mail left out; `messages acknowledge` moves the mark")
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")
221
228
  .option("--new", "what changed since the last `review --new` of this mailbox, a point per thread — for scheduled runs")
222
229
  .addHelpText("after", `
223
230
  Without a date, --unanswered and --waiting look at threads active in the last ${OWED_DAYS} days.
@@ -1,3 +1,4 @@
1
+ export { mailOwnText } from "../conversations/mail-text.js";
1
2
  export { mailAccountKey } from "./account-key.js";
2
3
  export { DEFAULT_ATTACHMENT_POLICY, type MailAttachmentPolicy, POLICY_SKIPS } from "./attachments.js";
3
4
  export { BULK, bulkBox } from "./bulk.js";
@@ -28,6 +29,7 @@ export { type MailCriteria, mailCriteriaOf, narrows } from "./server-query.js";
28
29
  export { type MailServerAccess, type MailServerRequest, searchMailServer } from "./server-search.js";
29
30
  export type { Listed, MailSource, Quick, Searched } from "./source.js";
30
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";
31
33
  export { listedOf, type StoredMail } from "./stored.js";
32
34
  export { MAX_ATTACHMENT_CHARS, type MailAccount, type MailIndexed, type MailSyncOptions, type MailSyncResult, RETRYABLE_ATTACHMENTS, syncMail, } from "./sync.js";
33
35
  export { readEmailWhole, type WholeAttachment } from "./whole.js";
@@ -1,3 +1,4 @@
1
+ export { mailOwnText } from "../conversations/mail-text.js";
1
2
  export { mailAccountKey } from "./account-key.js";
2
3
  export { DEFAULT_ATTACHMENT_POLICY, POLICY_SKIPS } from "./attachments.js";
3
4
  export { BULK, bulkBox } from "./bulk.js";
@@ -27,6 +28,7 @@ export { DEMOTE_AT, demotedSenders, SCORE_VERSION, scoreSenders, } from "./sende
27
28
  export { mailCriteriaOf, narrows } from "./server-query.js";
28
29
  export { searchMailServer } from "./server-search.js";
29
30
  export { SEARCH_REFUSED, searchRefused } from "./source.js";
31
+ export { MAIL_STATS_BY, mailStats, mailStatsItem, } from "./stats.js";
30
32
  export { listedOf } from "./stored.js";
31
33
  export { MAX_ATTACHMENT_CHARS, RETRYABLE_ATTACHMENTS, syncMail, } from "./sync.js";
32
34
  export { readEmailWhole } from "./whole.js";
@@ -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
+ });
@@ -21,8 +21,8 @@ export declare const readEmailWhole: ({ store, source, address, externalId, fold
21
21
  address: string;
22
22
  externalId: string;
23
23
  folders: () => Promise<readonly string[]>;
24
- maxBodyChars: number;
25
- maxAttachmentChars: number;
26
- loadEngine: LoadEngine;
27
- now: number;
24
+ maxBodyChars?: number;
25
+ maxAttachmentChars?: number;
26
+ loadEngine?: LoadEngine;
27
+ now?: number;
28
28
  }) => Promise<WholeAttachment[]>;
@@ -1,9 +1,10 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
+ import { importEngine } from "../attachments/extract.js";
2
3
  import { mailAccountKey } from "./account-key.js";
3
- import { readAttachments } from "./attachments.js";
4
+ import { MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
4
5
  import { parsedEmail } from "./email-input.js";
5
6
  import { mailHeadersOf } from "./headers.js";
6
- import { parseMail } from "./parse.js";
7
+ import { MAX_BODY_CHARS, parseMail } from "./parse.js";
7
8
  import { PARTIAL } from "./partial.js";
8
9
  /**
9
10
  * Reads one stored email from its source again and every attachment of it, whatever its size: what a person asks for
@@ -11,7 +12,7 @@ import { PARTIAL } from "./partial.js";
11
12
  * takes it; the files are only answered, never written. Gmail's email is found by its id in `folders`; elsewhere it is
12
13
  * read at its newest stored place, trusted only when `identify` gives the stored id for the bytes read there.
13
14
  */
14
- export const readEmailWhole = async ({ store, source, address, externalId, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, }) => {
15
+ export const readEmailWhole = async ({ store, source, address, externalId, folders, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, loadEngine = importEngine, now = Date.now(), }) => {
15
16
  const { id: accountId } = await store.accounts.storedAccount(mailAccountKey(address));
16
17
  const stored = await store.mail.emailPlace(accountId, externalId);
17
18
  if (stored === null)
@@ -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";
@@ -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";