@wirecat/cli-core 0.558.0 → 0.560.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 (92) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/background/schedules.d.ts +2 -0
  3. package/dist/cli/effects.d.ts +13 -2
  4. package/dist/cli/effects.js +23 -2
  5. package/dist/cli/index.d.ts +2 -2
  6. package/dist/cli/index.js +2 -2
  7. package/dist/cli/meeting-findings-input.d.ts +3 -0
  8. package/dist/cli/meeting-findings-input.js +4 -0
  9. package/dist/cli/messenger/conversation-answers.d.ts +3 -0
  10. package/dist/cli/messenger/message-answers.d.ts +3 -0
  11. package/dist/cli/messenger/message-writes.d.ts +6 -0
  12. package/dist/cli/schedule/schedule-command.d.ts +6 -1
  13. package/dist/cli/schedule/schedule-command.js +24 -2
  14. package/dist/domain/records.d.ts +161 -0
  15. package/dist/domain/records.js +234 -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 +190 -0
  20. package/dist/entities/index.js +3 -0
  21. package/dist/entities/listener.d.ts +12 -0
  22. package/dist/entities/mail-findings.d.ts +116 -0
  23. package/dist/entities/mail-findings.js +68 -0
  24. package/dist/entities/message-context.d.ts +9 -0
  25. package/dist/entities/message-write.d.ts +12 -0
  26. package/dist/entities/message.d.ts +6 -0
  27. package/dist/entities/message.js +6 -0
  28. package/dist/entities/store-records.d.ts +3 -0
  29. package/dist/errors/reasons.js +11 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/mail/cli/marks.d.ts +1 -0
  33. package/dist/mail/cli/marks.js +11 -4
  34. package/dist/mail/cli/review.js +10 -2
  35. package/dist/mail/findings.d.ts +55 -0
  36. package/dist/mail/findings.js +140 -0
  37. package/dist/mail/index.d.ts +2 -1
  38. package/dist/mail/index.js +2 -1
  39. package/dist/mail/marks.d.ts +10 -3
  40. package/dist/mail/marks.js +34 -5
  41. package/dist/meetings/findings-rules.d.ts +24 -0
  42. package/dist/meetings/findings-rules.js +136 -116
  43. package/dist/meetings/index.d.ts +2 -1
  44. package/dist/meetings/index.js +2 -1
  45. package/dist/meetings/mcp/index.d.ts +15 -15
  46. package/dist/runs/secrets.d.ts +2 -0
  47. package/dist/runs/secrets.js +2 -0
  48. package/dist/services/index.d.ts +1 -0
  49. package/dist/services/index.js +1 -0
  50. package/dist/services/ingest.d.ts +45 -0
  51. package/dist/services/ingest.js +80 -0
  52. package/dist/services/proposals/kinds/mail-findings.d.ts +8 -0
  53. package/dist/services/proposals/kinds/mail-findings.js +87 -0
  54. package/dist/services/proposals/registry.js +3 -1
  55. package/dist/services/proposals/types.d.ts +2 -0
  56. package/dist/store/decisions.d.ts +1 -1
  57. package/dist/store/decisions.js +1 -1
  58. package/dist/store/index.d.ts +2 -0
  59. package/dist/store/index.js +1 -0
  60. package/dist/store/mail-findings.d.ts +51 -0
  61. package/dist/store/mail-findings.js +1 -0
  62. package/dist/store/path.d.ts +2 -0
  63. package/dist/store/path.js +21 -1
  64. package/dist/store/sqlite/accounts.js +4 -0
  65. package/dist/store/sqlite/email-activity.d.ts +8 -0
  66. package/dist/store/sqlite/email-activity.js +31 -5
  67. package/dist/store/sqlite/findings-core.d.ts +1 -0
  68. package/dist/store/sqlite/findings-core.js +1 -1
  69. package/dist/store/sqlite/mail-findings.d.ts +3 -0
  70. package/dist/store/sqlite/mail-findings.js +333 -0
  71. package/dist/store/sqlite/manifest.js +2 -0
  72. package/dist/store/sqlite/memories.d.ts +4 -1
  73. package/dist/store/sqlite/memories.js +48 -43
  74. package/dist/store/sqlite/migrations.generated.js +16 -0
  75. package/dist/store/sqlite/open-store.d.ts +1 -1
  76. package/dist/store/sqlite/open-store.js +4 -1
  77. package/dist/store/sqlite/open.js +2 -1
  78. package/dist/store/sqlite/proposed-actions.js +2 -1
  79. package/dist/store/sqlite/records.d.ts +81 -0
  80. package/dist/store/sqlite/records.js +372 -0
  81. package/dist/store/sqlite/schema.d.ts +353 -0
  82. package/dist/store/sqlite/schema.js +37 -0
  83. package/dist/store/sqlite/tasks.d.ts +2 -0
  84. package/dist/store/sqlite/tasks.js +26 -24
  85. package/dist/store/sqlite/test-accounts.d.ts +8 -0
  86. package/dist/store/sqlite/test-accounts.js +31 -0
  87. package/dist/store/sqlite/work-tasks.d.ts +1 -0
  88. package/dist/store/sqlite/work-tasks.js +6 -0
  89. package/dist/store/store.d.ts +6 -0
  90. package/dist/store/store.js +10 -0
  91. package/package.json +4 -2
  92. package/schemas/records-envelope.v1.schema.json +108 -0
@@ -0,0 +1,68 @@
1
+ import * as v from "valibot";
2
+ import { FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, WORK_TASK_TYPES } from "../domain/knowledge.js";
3
+ import { author, id, reference, text, time } from "./fields.js";
4
+ const evidence = v.strictObject({ reference, fingerprint: text });
5
+ const base = { id, evidence: v.array(evidence) };
6
+ const finding = v.variant("kind", [
7
+ v.strictObject({
8
+ ...base,
9
+ kind: v.literal("task"),
10
+ project: v.optional(reference),
11
+ type: v.picklist(WORK_TASK_TYPES),
12
+ title: text,
13
+ reason: text,
14
+ commitment: v.picklist(["explicit", "inferred"]),
15
+ due_at: v.optional(time),
16
+ due_evidence: v.optional(v.array(reference)),
17
+ assignee: v.optional(reference),
18
+ assignee_evidence: v.optional(v.array(reference)),
19
+ }),
20
+ v.strictObject({
21
+ ...base,
22
+ kind: v.literal("fact"),
23
+ project: v.optional(reference),
24
+ subject: v.optional(reference),
25
+ type: v.picklist(FACT_TYPES),
26
+ statement: text,
27
+ }),
28
+ v.strictObject({
29
+ ...base,
30
+ kind: v.literal("task_update"),
31
+ task: reference,
32
+ change: v.picklist(["close", "due", "detail"]),
33
+ due_at: v.optional(time),
34
+ detail: v.optional(text),
35
+ reason: text,
36
+ }),
37
+ v.strictObject({
38
+ ...base,
39
+ kind: v.literal("memory"),
40
+ memory_kind: v.picklist(MEMORY_KINDS),
41
+ scope: v.picklist(MEMORY_SCOPES),
42
+ body: text,
43
+ subject: v.optional(reference),
44
+ project: v.optional(reference),
45
+ }),
46
+ v.strictObject({ ...base, kind: v.literal("link"), from: reference, to: reference, reason: text }),
47
+ v.strictObject({ ...base, kind: v.literal("new_contact"), address: text, name: text, reason: v.optional(text) }),
48
+ ]);
49
+ const outcome = v.strictObject({
50
+ id,
51
+ fingerprint: text,
52
+ status: v.picklist(["accepted", "rejected"]),
53
+ reference: v.nullable(reference),
54
+ reused: v.boolean(),
55
+ });
56
+ /** A mail findings batch: what an agent read in one mailbox's new mail, waiting for the owner. */
57
+ export const mailFindings = v.strictObject({
58
+ id,
59
+ account: v.strictObject({ provider: text, account: text }),
60
+ input: v.strictObject({ schema_version: v.literal(1), key: text, extractor: text, findings: v.array(finding) }),
61
+ fingerprint: text,
62
+ status: v.picklist(["proposed", "reviewed"]),
63
+ author,
64
+ decided_by: v.nullable(v.strictObject({ type: v.picklist(["person", "bot"]), id })),
65
+ decided_at: v.nullable(time),
66
+ previous: v.array(outcome),
67
+ receipt: v.nullable(v.strictObject({ preview_fingerprint: text, accepted: v.array(id), items: v.array(outcome) })),
68
+ });
@@ -222,6 +222,9 @@ export declare const messageThread: v.StrictObjectSchema<{
222
222
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
223
223
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
224
224
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
225
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
226
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
227
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
225
228
  }, undefined>, undefined>;
226
229
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
227
230
  }, undefined>, undefined>;
@@ -466,6 +469,9 @@ export declare const messageContext: v.UnionSchema<[v.StrictObjectSchema<{
466
469
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
467
470
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
468
471
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
472
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
473
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
474
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
469
475
  }, undefined>, undefined>;
470
476
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
471
477
  }, undefined>, v.StrictObjectSchema<{
@@ -691,6 +697,9 @@ export declare const messageContext: v.UnionSchema<[v.StrictObjectSchema<{
691
697
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
692
698
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
693
699
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
700
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
701
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
702
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
694
703
  }, undefined>, undefined>;
695
704
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
696
705
  }, undefined>, undefined>;
@@ -218,6 +218,9 @@ export declare const messageWrite: v.StrictObjectSchema<{
218
218
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
219
219
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
220
220
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
221
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
222
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
223
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
221
224
  }, undefined>, undefined>;
222
225
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
223
226
  }, undefined>;
@@ -439,6 +442,9 @@ export declare const messageWrite: v.StrictObjectSchema<{
439
442
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
440
443
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
441
444
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
445
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
446
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
447
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
442
448
  }, undefined>, undefined>;
443
449
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
444
450
  }, undefined>, undefined>, undefined>;
@@ -668,6 +674,9 @@ export declare const messageSend: v.UnionSchema<[v.StrictObjectSchema<{
668
674
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
669
675
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
670
676
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
677
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
678
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
679
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
671
680
  }, undefined>, undefined>;
672
681
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
673
682
  }, undefined>;
@@ -889,6 +898,9 @@ export declare const messageSend: v.UnionSchema<[v.StrictObjectSchema<{
889
898
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
890
899
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
891
900
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
901
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
902
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
903
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
892
904
  }, undefined>, undefined>;
893
905
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
894
906
  }, undefined>, undefined>, undefined>;
@@ -244,6 +244,12 @@ export declare const message: v.StrictObjectSchema<{
244
244
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
245
245
  /** Mail `review`: the mail rule that decided how it is stored, by its id in `rules list`; `null` when none. */
246
246
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
247
+ /** Mail `review`: the evidence fingerprint a finding cites this email by. */
248
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
249
+ /** Mail `review`: no earlier stored email, in any account, came from this address. */
250
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
251
+ /** Mail `review`: how many identities the sender's person has; 1 is this address alone. */
252
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
247
253
  }, undefined>, undefined>;
248
254
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
249
255
  }, undefined>;
@@ -109,6 +109,12 @@ export const message = v.strictObject({
109
109
  list_id: v.optional(v.nullable(text)),
110
110
  /** Mail `review`: the mail rule that decided how it is stored, by its id in `rules list`; `null` when none. */
111
111
  rule_id: v.optional(v.nullable(v.number())),
112
+ /** Mail `review`: the evidence fingerprint a finding cites this email by. */
113
+ fingerprint: v.optional(v.pipe(text, v.regex(/^[a-f0-9]{64}$/))),
114
+ /** Mail `review`: no earlier stored email, in any account, came from this address. */
115
+ new_sender: v.optional(v.boolean()),
116
+ /** Mail `review`: how many identities the sender's person has; 1 is this address alone. */
117
+ sender_identities: v.optional(v.number()),
112
118
  })),
113
119
  provider_metadata: providerMetadata,
114
120
  });
@@ -241,6 +241,9 @@ export declare const storeExport: v.UnionSchema<[v.StrictObjectSchema<{
241
241
  readonly bulk: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
242
242
  readonly list_id: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
243
243
  readonly rule_id: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
244
+ readonly fingerprint: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
245
+ readonly new_sender: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
246
+ readonly sender_identities: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
244
247
  }, undefined>, undefined>;
245
248
  readonly provider_metadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
246
249
  }, undefined>, v.StrictObjectSchema<{
@@ -44,6 +44,8 @@ const OTHERS = [
44
44
  validation("meeting-findings.stale", "meeting findings changed since they were read; stage a new batch"),
45
45
  validation("meeting-findings.dedicated-review", "review meeting findings through the dedicated findings command"),
46
46
  validation("evidence-findings.stale", "evidence findings changed since they were read; stage a new batch"),
47
+ validation("mail-findings.stale", "mail findings cite mail that changed since it was read; stage a new batch"),
48
+ validation("mail-findings.partial", "a cited email is not stored whole yet; cite it once it is"),
47
49
  validation("evidence-findings.dedicated-review", "review evidence findings through the dedicated findings command"),
48
50
  validation("document.changed", "the document changed while it was read; nothing was saved"),
49
51
  ["document.not-imported", "not_found", "the document is not imported in this source account"],
@@ -151,6 +153,15 @@ const OTHERS = [
151
153
  validation("finance.ref", "the reference names no invoice or payment"),
152
154
  validation("finance.status", "the invoice's status does not allow this"),
153
155
  validation("finance.total", "the invoice's total is not the sum of its lines"),
156
+ validation("record.entity", "the invoice or payment the record stands for was refused"),
157
+ ["record.map-owner-only", "permission_error", "only the owner says which entity a source record is"],
158
+ validation("record.map-type", "a record maps only to an entity of its own type"),
159
+ validation("record.mapped", "the record already stands for another entity"),
160
+ validation("record.not-found", "the store holds no such record to delete"),
161
+ validation("record.invalid", "a field of the record is not the kind the entity needs"),
162
+ validation("record.reference-not-found", "a reference in the record's fields is not in the store"),
163
+ validation("record.relation-ambiguous", "the record names more than one target for a role that takes one"),
164
+ validation("record.stale-revision", "the store holds a newer revision of the record"),
154
165
  validation("memory.evidence-required", "a memory needs its evidence"),
155
166
  validation("memory.scope-required", "a memory needs its scope"),
156
167
  validation("money.amount", "the amount is not a decimal string in the currency's scale"),
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, k
14
14
  export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
15
15
  export type { RankingComponent, RankingInput, RankingMeasure, RankingOptions, RankingTarget, RankingWeights, ScorePreset, } from "./domain/rankings-options.js";
16
16
  export { rankingOptions } from "./domain/rankings-options.js";
17
+ export { type EnvelopeRecord, type EnvelopeSource, envelopeOfJsonl, type IngestError, RECORD_ENTITY_TYPES, RECORD_OPERATIONS, RECORDS_ENVELOPE_SCHEMA, RECORDS_SCHEMA_VERSION, type RecordEntityType, type RecordOperation, type RecordRelation, type RecordsEnvelope, } from "./domain/records.js";
17
18
  export { canonicalReference, formatReference, looksLikeReference, parseReference, REF_KINDS, type Reference, type RefKindName, type RefKindSpec, } from "./domain/references.js";
18
19
  export { normalizeTag, TAG_TYPES, type TagType } from "./domain/tags.js";
19
20
  export { type AppErrors, DETAILS as CORE_DETAILS, failureContext, REASONS as CORE_REASONS } from "./errors/reasons.js";
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { CURRENCY_SCALES, currencyOf, minorUnits, moneyLabel, moneyOf, } from ".
9
9
  export { countedPoll } from "./domain/polls.js";
10
10
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
11
11
  export { rankingOptions } from "./domain/rankings-options.js";
12
+ export { envelopeOfJsonl, RECORD_ENTITY_TYPES, RECORD_OPERATIONS, RECORDS_ENVELOPE_SCHEMA, RECORDS_SCHEMA_VERSION, } from "./domain/records.js";
12
13
  export { canonicalReference, formatReference, looksLikeReference, parseReference, REF_KINDS, } from "./domain/references.js";
13
14
  export { normalizeTag, TAG_TYPES } from "./domain/tags.js";
14
15
  export { DETAILS as CORE_DETAILS, failureContext, REASONS as CORE_REASONS } from "./errors/reasons.js";
@@ -14,6 +14,7 @@ export declare const mailMark: v.StrictObjectSchema<{
14
14
  readonly envelope: v.NumberSchema<undefined>;
15
15
  readonly partial: v.NumberSchema<undefined>;
16
16
  readonly draft: v.NumberSchema<undefined>;
17
+ readonly suspicious: v.NumberSchema<undefined>;
17
18
  }, undefined>;
18
19
  readonly updated_at: v.StringSchema<undefined>;
19
20
  }, undefined>;
@@ -3,7 +3,7 @@ import { Command } from "commander";
3
3
  import * as v from "valibot";
4
4
  import { answerEntity } from "../../cli/answer-entity.js";
5
5
  import { outputFor } from "../../cli/context.js";
6
- import { destroysNothing, LOCAL_WRITE } from "../../cli/effects.js";
6
+ import { LOCAL_WRITE, proposesOnly } from "../../cli/effects.js";
7
7
  import { itemOf, renderAnswer } from "../../cli/envelope.js";
8
8
  import { acknowledgeMark } from "../marks.js";
9
9
  const iso = (ms) => (ms === null ? null : new Date(ms).toISOString());
@@ -14,7 +14,13 @@ export const mailMark = v.strictObject({
14
14
  through_received_at: v.nullable(v.string()),
15
15
  held: v.number(),
16
16
  read: v.number(),
17
- skipped: v.strictObject({ bulk: v.number(), envelope: v.number(), partial: v.number(), draft: v.number() }),
17
+ skipped: v.strictObject({
18
+ bulk: v.number(),
19
+ envelope: v.number(),
20
+ partial: v.number(),
21
+ draft: v.number(),
22
+ suspicious: v.number(),
23
+ }),
18
24
  updated_at: v.string(),
19
25
  });
20
26
  export const markItem = (mark) => ({
@@ -28,12 +34,13 @@ export const markItem = (mark) => ({
28
34
  });
29
35
  export const markText = (mark) => `${mark.holder} through ${mark.throughId}${mark.throughAt === null ? "" : ` (${iso(mark.throughAt)})`} ` +
30
36
  `read ${mark.read} held ${mark.held.length} skipped bulk ${mark.skipped.bulk}, envelope ${mark.skipped.envelope}, ` +
31
- `partial ${mark.skipped.partial}, draft ${mark.skipped.draft} updated ${iso(mark.updatedAt)}\n`;
37
+ `partial ${mark.skipped.partial}, draft ${mark.skipped.draft}, suspicious ${mark.skipped.suspicious} ` +
38
+ `updated ${iso(mark.updatedAt)}\n`;
32
39
  /**
33
40
  * `messages acknowledge`: a holder says it is done with what `review --holder` gave it, and its mark moves there. It
34
41
  * only moves the holder's own bookkeeping forward, so a read-only scheduled run may do it.
35
42
  */
36
- export const acknowledgeCommand = (mail) => answerEntity(destroysNothing(annotate(new Command("acknowledge"), {
43
+ export const acknowledgeCommand = (mail) => answerEntity(proposesOnly(annotate(new Command("acknowledge"), {
37
44
  effects: LOCAL_WRITE,
38
45
  examples: [
39
46
  `${mail.app.command} personal messages acknowledge --holder mail-triage --cursor 9123.1762090000000`,
@@ -114,6 +114,13 @@ const reviewAnswer = (found, mailbox, fresh) => {
114
114
  bulk: message.marks?.bulk ?? false,
115
115
  list_id: message.marks?.listId ?? null,
116
116
  rule_id: message.marks?.ruleId ?? null,
117
+ ...(message.marks
118
+ ? {
119
+ fingerprint: message.marks.fingerprint,
120
+ new_sender: message.marks.newSender,
121
+ sender_identities: message.marks.senderIdentities,
122
+ }
123
+ : {}),
117
124
  },
118
125
  })),
119
126
  limit: Math.max(1, read.length),
@@ -202,11 +209,12 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
202
209
  .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")
203
210
  .option("--waiting [duration]", "only threads where you wrote last and nobody answered for this long — 3d; 7d if not given")
204
211
  .option("--holder <name>", "read for this holder — an agent or a pipeline — what is stored past its processing mark, by store order; " +
205
- "bulk, envelopes, drafts and partial mail left out; `messages acknowledge` moves the mark")
212
+ "bulk, envelopes, drafts, suspicious senders and partial mail left out; `messages acknowledge` moves the mark")
206
213
  .option("--new", "what changed since the last `review --new` of this mailbox, a point per thread — for scheduled runs")
207
214
  .addHelpText("after", `
208
215
  Without a date, --unanswered and --waiting look at threads active in the last ${OWED_DAYS} days.
209
- Every email is shown, mail sent to many too; each item's email says bulk, list_id and rule_id, and its sender_score.
216
+ Every email is shown, mail sent to many too; each item's email says bulk, list_id and rule_id, and its sender_score,
217
+ the fingerprint a finding cites it by, new_sender (no earlier email from the address) and sender_identities.
210
218
  bulk:false leaves out threads whose newest email is mail sent to many, a list's or recorded by an envelope rule.
211
219
  Mail newer than the last finished sync waits for the next review. Each reader keeps its own --new point: you,
212
220
  an agent over MCP, and each scheduled run.`)
@@ -0,0 +1,55 @@
1
+ import { MEMORY_KINDS, MEMORY_SCOPES } from "../domain/knowledge.js";
2
+ import { type FactFinding, type FindingEvidence, type TaskFinding } from "../meetings/index.js";
3
+ export { MAIL_FINDINGS_KIND } from "../store/mail-findings.js";
4
+ /** At most this many findings in one run's batch, and per sender in it. */
5
+ export declare const MAIL_FINDINGS_PER_RUN = 30;
6
+ export declare const MAIL_FINDINGS_PER_SENDER = 5;
7
+ interface Cited {
8
+ id: string;
9
+ evidence: FindingEvidence[];
10
+ }
11
+ export type MailTaskFinding = TaskFinding;
12
+ export type MailFactFinding = FactFinding;
13
+ export interface MailTaskUpdateFinding extends Cited {
14
+ kind: "task_update";
15
+ task: string;
16
+ change: "close" | "due" | "detail";
17
+ dueAt?: string;
18
+ detail?: string;
19
+ reason: string;
20
+ }
21
+ export interface MailMemoryFinding extends Cited {
22
+ kind: "memory";
23
+ memoryKind: (typeof MEMORY_KINDS)[number];
24
+ scope: (typeof MEMORY_SCOPES)[number];
25
+ body: string;
26
+ subject?: string;
27
+ project?: string;
28
+ }
29
+ /** Address → person, person → organization, or thread → project. */
30
+ export interface MailLinkFinding extends Cited {
31
+ kind: "link";
32
+ from: string;
33
+ to: string;
34
+ reason: string;
35
+ }
36
+ export interface MailNewContactFinding extends Cited {
37
+ kind: "new_contact";
38
+ address: string;
39
+ name: string;
40
+ reason?: string;
41
+ }
42
+ export type MailFinding = MailTaskFinding | MailFactFinding | MailTaskUpdateFinding | MailMemoryFinding | MailLinkFinding | MailNewContactFinding;
43
+ export interface MailFindingsInput {
44
+ schemaVersion: 1;
45
+ /** `<holder>:<through_id>`: one batch per run of a holder over the mail it was given. */
46
+ key: string;
47
+ extractor: string;
48
+ findings: MailFinding[];
49
+ }
50
+ /**
51
+ * The shape of a mail findings batch: 1–30 findings, each citing 1–8 `msg:email/…` references with fingerprints.
52
+ * What needs the store — the fingerprints, partial mail, the per-sender cap, the targets — the store checks.
53
+ */
54
+ export declare const validateMailFindings: (input: MailFindingsInput) => MailFindingsInput;
55
+ export declare const mailFindingFingerprint: ({ id: _id, ...finding }: MailFinding) => string;
@@ -0,0 +1,140 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { MEMORY_KINDS, MEMORY_SCOPES } from "../domain/knowledge.js";
3
+ import { formatLocator, parseLocator } from "../domain/locator.js";
4
+ import { parseReference } from "../domain/references.js";
5
+ import { findingFields, findingHash, findingId, findingText, meetingTimestamp, validateBatch, validateFinding, validEvidence, } from "../meetings/index.js";
6
+ export { MAIL_FINDINGS_KIND } from "../store/mail-findings.js";
7
+ /** At most this many findings in one run's batch, and per sender in it. */
8
+ export const MAIL_FINDINGS_PER_RUN = 30;
9
+ export const MAIL_FINDINGS_PER_SENDER = 5;
10
+ const invalid = (message) => {
11
+ throw new CliError("validation_error", message);
12
+ };
13
+ const emailEvidence = (raw) => {
14
+ try {
15
+ const locator = parseLocator(raw);
16
+ if (locator.provider === "email")
17
+ return formatLocator(locator);
18
+ }
19
+ catch { }
20
+ return invalid("mail finding evidence is a msg:email/… reference");
21
+ };
22
+ const RULES = {
23
+ plural: "mail findings",
24
+ singular: "mail finding",
25
+ cited: "reference",
26
+ reference: emailEvidence,
27
+ anySubject: true,
28
+ inboxTasks: true,
29
+ };
30
+ const typeOf = (reference) => {
31
+ try {
32
+ const parsed = parseReference(reference);
33
+ return parsed.type === "contact" || parsed.type === "chat" ? `${parsed.type}:${parsed.provider}` : parsed.type;
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ };
39
+ const LINKS = {
40
+ "contact:email": "person",
41
+ person: "organization",
42
+ "chat:email": "project",
43
+ };
44
+ const ADDRESS = /^[^\s@<>]+@[^\s@<>]+$/;
45
+ const one = (raw, ids) => {
46
+ if (!raw || typeof raw !== "object")
47
+ return invalid("a finding is an object");
48
+ if (raw.kind === "task" || raw.kind === "fact")
49
+ return validateFinding(raw, RULES, ids);
50
+ const id = findingId(raw.id, ids);
51
+ const evidence = validEvidence(raw.evidence, RULES);
52
+ const reference = (value, pattern, what) => {
53
+ const text = findingText(value, 400);
54
+ if (!pattern.test(text))
55
+ return invalid(`${what} is not a reference of the kind it takes: ${text}`);
56
+ return text;
57
+ };
58
+ switch (raw.kind) {
59
+ case "task_update": {
60
+ findingFields(raw, ["id", "kind", "task", "change", "dueAt", "detail", "reason", "evidence"], RULES.singular);
61
+ const task = reference(raw.task, /^task:[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/, "a task update's task");
62
+ const change = raw.change;
63
+ if (change !== "close" && change !== "due" && change !== "detail")
64
+ return invalid("a task update's change is close, due or detail");
65
+ if ((change === "due") !== (raw.dueAt !== undefined))
66
+ return invalid("a due change, and only it, gives due_at");
67
+ if ((change === "detail") !== (raw.detail !== undefined))
68
+ return invalid("a detail change, and only it, gives detail");
69
+ return {
70
+ id,
71
+ kind: "task_update",
72
+ task,
73
+ change,
74
+ ...(raw.dueAt === undefined
75
+ ? {}
76
+ : { dueAt: meetingTimestamp(raw.dueAt, "a task update's due date") }),
77
+ ...(raw.detail === undefined ? {} : { detail: findingText(raw.detail, 2000) }),
78
+ reason: findingText(raw.reason, 2000),
79
+ evidence,
80
+ };
81
+ }
82
+ case "memory": {
83
+ findingFields(raw, ["id", "kind", "memoryKind", "scope", "body", "subject", "project", "evidence"], RULES.singular);
84
+ if (!MEMORY_KINDS.includes(raw.memoryKind))
85
+ return invalid(`a memory's memory_kind is ${MEMORY_KINDS.join(", ")}`);
86
+ if (!MEMORY_SCOPES.includes(raw.scope))
87
+ return invalid("a memory's scope is personal or work");
88
+ if (raw.subject === undefined && raw.project === undefined)
89
+ return invalid("a memory is about a subject or a project");
90
+ return {
91
+ id,
92
+ kind: "memory",
93
+ memoryKind: raw.memoryKind,
94
+ scope: raw.scope,
95
+ body: findingText(raw.body, 20_000),
96
+ ...(raw.subject === undefined
97
+ ? {}
98
+ : { subject: reference(raw.subject, /^(person|organization|project):[1-9]\d*$/, "a memory's subject") }),
99
+ ...(raw.project === undefined ? {} : { project: reference(raw.project, /^project:[1-9]\d*$/, "a project") }),
100
+ evidence,
101
+ };
102
+ }
103
+ case "link": {
104
+ findingFields(raw, ["id", "kind", "from", "to", "reason", "evidence"], RULES.singular);
105
+ const from = findingText(raw.from, 400);
106
+ const to = findingText(raw.to, 80);
107
+ const wanted = LINKS[typeOf(from) ?? ""];
108
+ if (!wanted || typeOf(to) !== wanted || !/:[1-9]\d*$/.test(to))
109
+ return invalid("a link goes from contact:email/<address> to person:, person: to organization:, or chat:email/… to project:");
110
+ return { id, kind: "link", from, to, reason: findingText(raw.reason, 2000), evidence };
111
+ }
112
+ case "new_contact": {
113
+ findingFields(raw, ["id", "kind", "address", "name", "reason", "evidence"], RULES.singular);
114
+ const address = findingText(raw.address, 320).toLowerCase();
115
+ if (!ADDRESS.test(address))
116
+ return invalid("a new contact's address is an email address");
117
+ return {
118
+ id,
119
+ kind: "new_contact",
120
+ address,
121
+ name: findingText(raw.name, 200),
122
+ ...(raw.reason === undefined ? {} : { reason: findingText(raw.reason, 2000) }),
123
+ evidence,
124
+ };
125
+ }
126
+ default:
127
+ return invalid("a mail finding is a task, task_update, fact, memory, link or new_contact");
128
+ }
129
+ };
130
+ /**
131
+ * The shape of a mail findings batch: 1–30 findings, each citing 1–8 `msg:email/…` references with fingerprints.
132
+ * What needs the store — the fingerprints, partial mail, the per-sender cap, the targets — the store checks.
133
+ */
134
+ export const validateMailFindings = (input) => {
135
+ const batch = validateBatch(input, RULES, MAIL_FINDINGS_PER_RUN, one);
136
+ if (!/^[a-z0-9][a-z0-9-]{0,39}:[1-9]\d*$/.test(batch.key))
137
+ return invalid("a mail findings key is <holder>:<through_id>, such as mail-triage:9123456");
138
+ return batch;
139
+ };
140
+ export const mailFindingFingerprint = ({ id: _id, ...finding }) => findingHash(finding);
@@ -1,12 +1,13 @@
1
1
  export { mailAccountKey } from "./account-key.js";
2
2
  export { BULK, bulkBox } from "./bulk.js";
3
3
  export { factsOf } from "./facts.js";
4
+ export { MAIL_FINDINGS_KIND, MAIL_FINDINGS_PER_RUN, MAIL_FINDINGS_PER_SENDER, type MailFinding, type MailFindingsInput, mailFindingFingerprint, validateMailFindings, } from "./findings.js";
4
5
  export { GMAIL_ALL_MAIL, mailFolders } from "./folders.js";
5
6
  export { dropMissing, type Gone, tooManyGone } from "./gone.js";
6
7
  export { type AuthVerdicts, HEADER_FIELDS, headersOf, type MailHeaders, mailHeadersOf } from "./headers.js";
7
8
  export { type MailIdsInput, mailIds } from "./ids.js";
8
9
  export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
9
- export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, type MailMark, type MarkRead, markOf, marksOf, readPastMark, type SkipReason, } from "./marks.js";
10
+ export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, holderHasMail, MARK_BATCH, type MailMark, type MarkRead, markOf, marksOf, readPastMark, type SkipReason, } from "./marks.js";
10
11
  export { type MailTurn, mailTurn } from "./owed.js";
11
12
  export { type Address, htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, type ParsedMail, parseMail, } from "./parse.js";
12
13
  export { PARTIAL, type PartialOutcome } from "./partial.js";
@@ -1,12 +1,13 @@
1
1
  export { mailAccountKey } from "./account-key.js";
2
2
  export { BULK, bulkBox } from "./bulk.js";
3
3
  export { factsOf } from "./facts.js";
4
+ export { MAIL_FINDINGS_KIND, MAIL_FINDINGS_PER_RUN, MAIL_FINDINGS_PER_SENDER, mailFindingFingerprint, validateMailFindings, } from "./findings.js";
4
5
  export { GMAIL_ALL_MAIL, mailFolders } from "./folders.js";
5
6
  export { dropMissing, tooManyGone } from "./gone.js";
6
7
  export { HEADER_FIELDS, headersOf, mailHeadersOf } from "./headers.js";
7
8
  export { mailIds } from "./ids.js";
8
9
  export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
9
- export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, markOf, marksOf, readPastMark, } from "./marks.js";
10
+ export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, holderHasMail, MARK_BATCH, markOf, marksOf, readPastMark, } from "./marks.js";
10
11
  export { mailTurn } from "./owed.js";
11
12
  export { htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, parseMail, } from "./parse.js";
12
13
  export { PARTIAL } from "./partial.js";
@@ -5,7 +5,7 @@ export declare const HOLDER: RegExp;
5
5
  export declare const MARK_BATCH = 200;
6
6
  /** A partial email waits this long to be stored whole, then is skipped. */
7
7
  export declare const HOLD_PARTIAL_MS: number;
8
- export type SkipReason = "bulk" | "envelope" | "partial" | "draft";
8
+ export type SkipReason = "bulk" | "envelope" | "partial" | "draft" | "suspicious";
9
9
  /**
10
10
  * How far one holder — an agent, a pipeline — has processed an account's mail, in the shared store (`sync_cursors`,
11
11
  * key `consumer:<holder>`). It counts by the store's insert order (`emails.id`), so mail stored late, older than what
@@ -28,7 +28,7 @@ export declare const marksOf: (store: MessageStore, mailbox: AccountKey) => Prom
28
28
  /** The mark's state, as a cursor `acknowledge` takes back: the last id read, and when it was read. */
29
29
  export declare const cursorOf: (throughId: number, readAt: number) => string;
30
30
  export interface MarkRead {
31
- /** The emails for the holder, oldest first; bulk, envelopes, drafts and partial mail left out. */
31
+ /** The emails for the holder, oldest first; bulk, envelopes, drafts, suspicious senders and partial mail left out. */
32
32
  emails: StoredHit[];
33
33
  /** What `acknowledge --cursor` takes once the holder is done with these. */
34
34
  cursor: string;
@@ -44,10 +44,17 @@ export interface MarkRead {
44
44
  * only; the mark moves when the holder acknowledges. A holder with no mark starts from now: its first read is
45
45
  * empty and gives the cursor of the newest stored email.
46
46
  */
47
- export declare const readPastMark: (store: MessageStore, mailbox: AccountKey, holder: string, { now, limit }?: {
47
+ export declare const readPastMark: (store: MessageStore, mailbox: AccountKey, holder: string, options?: {
48
48
  now?: number;
49
49
  limit?: number;
50
50
  }) => Promise<MarkRead>;
51
+ /**
52
+ * Whether the holder has mail to read in any stored mailbox — what a schedule asks before it starts the holder's
53
+ * agent, so a run with nothing new costs no model call. Mail that is only skipped (bulk, envelopes, drafts,
54
+ * suspicious senders) moves the holder's mark here, as the agent would have acknowledged it, or it would wait
55
+ * forever behind the mark. A holder with no mark yet has mail to read: its first run sets the mark.
56
+ */
57
+ export declare const holderHasMail: (store: MessageStore, holder: string, now?: number) => Promise<boolean>;
51
58
  /**
52
59
  * Moves the holder's mark to the cursor its read gave, never back. An email whole when it was read counts read; one
53
60
  * still partial, or changed after the read (it may have become whole since), is held and comes with the next read.