@dxos/pipeline-email 0.0.0 → 0.11.1

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 (66) hide show
  1. package/dist/lib/index.mjs +994 -0
  2. package/dist/lib/index.mjs.map +1 -0
  3. package/dist/lib/testing.mjs +49 -0
  4. package/dist/lib/testing.mjs.map +1 -0
  5. package/dist/types/src/corpus/digest.d.ts +0 -4
  6. package/dist/types/src/corpus/digest.d.ts.map +1 -1
  7. package/dist/types/src/corpus/topics.d.ts +12 -4
  8. package/dist/types/src/corpus/topics.d.ts.map +1 -1
  9. package/dist/types/src/fact-pipeline.d.ts +28 -3
  10. package/dist/types/src/fact-pipeline.d.ts.map +1 -1
  11. package/dist/types/src/index.d.ts +4 -0
  12. package/dist/types/src/index.d.ts.map +1 -1
  13. package/dist/types/src/internal/threads.d.ts +5 -1
  14. package/dist/types/src/internal/threads.d.ts.map +1 -1
  15. package/dist/types/src/model-policy.d.ts +17 -0
  16. package/dist/types/src/model-policy.d.ts.map +1 -0
  17. package/dist/types/src/model-policy.test.d.ts +2 -0
  18. package/dist/types/src/model-policy.test.d.ts.map +1 -0
  19. package/dist/types/src/stages/EmailStage.d.ts +149 -0
  20. package/dist/types/src/stages/EmailStage.d.ts.map +1 -0
  21. package/dist/types/src/stages/extract-facts-commit.d.ts +1 -1
  22. package/dist/types/src/stages/extract-facts-commit.d.ts.map +1 -1
  23. package/dist/types/src/stages/index.d.ts +2 -0
  24. package/dist/types/src/stages/index.d.ts.map +1 -1
  25. package/dist/types/src/stages/tag.d.ts +38 -0
  26. package/dist/types/src/stages/tag.d.ts.map +1 -0
  27. package/dist/types/src/stages/tag.test.d.ts +2 -0
  28. package/dist/types/src/stages/tag.test.d.ts.map +1 -0
  29. package/dist/types/src/topics-pipeline.d.ts +55 -0
  30. package/dist/types/src/topics-pipeline.d.ts.map +1 -0
  31. package/dist/types/src/topics-pipeline.test.d.ts +2 -0
  32. package/dist/types/src/topics-pipeline.test.d.ts.map +1 -0
  33. package/dist/types/src/types/index.d.ts +0 -1
  34. package/dist/types/src/types/index.d.ts.map +1 -1
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +22 -17
  37. package/src/corpus/digest.test.ts +1 -1
  38. package/src/corpus/digest.ts +6 -6
  39. package/src/corpus/topics.test.ts +72 -18
  40. package/src/corpus/topics.ts +61 -15
  41. package/src/fact-pipeline.ts +114 -4
  42. package/src/index.ts +4 -0
  43. package/src/internal/threads.test.ts +12 -0
  44. package/src/internal/threads.ts +11 -3
  45. package/src/model-policy.test.ts +21 -0
  46. package/src/model-policy.ts +45 -0
  47. package/src/stages/EmailStage.ts +398 -0
  48. package/src/stages/extract-facts-commit.ts +16 -7
  49. package/src/stages/index.ts +3 -0
  50. package/src/stages/tag.test.ts +98 -0
  51. package/src/stages/tag.ts +150 -0
  52. package/src/testing/email-pipeline.test.ts +9 -6
  53. package/src/topics-pipeline.test.ts +95 -0
  54. package/src/topics-pipeline.ts +106 -0
  55. package/src/types/index.ts +0 -1
  56. package/dist/lib/neutral/index.mjs +0 -650
  57. package/dist/lib/neutral/index.mjs.map +0 -7
  58. package/dist/lib/neutral/meta.json +0 -1
  59. package/dist/lib/neutral/testing/index.mjs +0 -48
  60. package/dist/lib/neutral/testing/index.mjs.map +0 -7
  61. package/dist/types/src/types/Topic.d.ts +0 -12
  62. package/dist/types/src/types/Topic.d.ts.map +0 -1
  63. package/dist/types/src/types/Topic.test.d.ts +0 -2
  64. package/dist/types/src/types/Topic.test.d.ts.map +0 -1
  65. package/src/types/Topic.test.ts +0 -25
  66. package/src/types/Topic.ts +0 -20
@@ -0,0 +1,149 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import * as Stream from 'effect/Stream';
3
+ import { Database, Obj } from '@dxos/echo';
4
+ import { Cursor } from '@dxos/link';
5
+ import { Stage } from '@dxos/pipeline';
6
+ import { TagIndex } from '@dxos/schema';
7
+ import { Message, Person } from '@dxos/types';
8
+ /**
9
+ * Reusable, generic email-processing pipeline stages: {@link Change} → {@link Change}, composing in any
10
+ * order, each simply recording what it found (`message.attachments`, `contact`, …) rather than writing
11
+ * directly. {@link toCommitUnit} is the terminal stage that turns those into a generic
12
+ * `Cursor.CommitUnit`, and must run last. Provider-specific decode/map stages construct the initial
13
+ * `Change` (an `Insert`, tagged from the start) and stay in each sync op.
14
+ */
15
+ /** An item carrying a body string that may contain HTML. */
16
+ export type Bodied = {
17
+ readonly body: string;
18
+ };
19
+ /**
20
+ * Normalizes an item's `body` from (possibly) HTML into markdown via `normalizeText` (turndown when
21
+ * HTML, passthrough for plaintext). Requires nothing from the Requirements channel. Written as a
22
+ * generic pipeable so the item type infers from the upstream stream (no type argument needed).
23
+ */
24
+ export declare const htmlToMarkdown: <In extends Bodied, E, R>(self: Stream.Stream<In, E, R>) => Stream.Stream<In, E, R>;
25
+ /**
26
+ * A new message to append, tagged `insert` from the moment a provider decodes it. The upstream dedup
27
+ * stage (`Cursor.dedupStage`) guarantees a message reaching this variant was never previously committed,
28
+ * so it's a one-time insert, never a resync/overwrite. The generic email stages act only on this variant
29
+ * of {@link Change}.
30
+ */
31
+ export type Insert = {
32
+ readonly _tag: 'insert';
33
+ readonly message: Message.Message;
34
+ readonly foreignId: string;
35
+ readonly key: number;
36
+ /** Provider label/folder tag URIs to apply on commit; omit for providers/stages that don't tag. */
37
+ readonly tagUris?: readonly string[];
38
+ /** Attachments fetched by a provider-specific stage upstream of {@link processAttachments}. */
39
+ readonly attachments?: readonly Attachment[];
40
+ /** Contact resolved by {@link extractContacts}, added to the database by {@link toCommitUnit}. */
41
+ readonly contact?: Person.Person;
42
+ /** Local drafts this synced message supersedes ({@link reconcileDrafts}), removed by {@link toCommitUnit}. */
43
+ readonly supersededDrafts?: readonly Message.Message[];
44
+ };
45
+ /**
46
+ * A label change on an already-committed message, resolved to its feed message's `entityId`.
47
+ * {@link toCommitUnit} turns it into an objectless commit unit that applies the tag adds/removes — no
48
+ * feed append, so the body stages ({@link processAttachments}, {@link extractContacts},
49
+ * {@link reconcileDrafts}) pass it through untouched.
50
+ */
51
+ export type Retag = {
52
+ readonly _tag: 'retag';
53
+ readonly foreignId: string;
54
+ readonly entityId: Obj.ID;
55
+ readonly addTagIds: readonly string[];
56
+ readonly removeTagIds: readonly string[];
57
+ };
58
+ /**
59
+ * A remote deletion of an already-committed message. Wired but STUBBED — {@link toCommitUnit} emits an
60
+ * objectless unit with no effect (see the TODO there) pending safe in-sync feed object deletion.
61
+ */
62
+ export type Delete = {
63
+ readonly _tag: 'delete';
64
+ readonly foreignId: string;
65
+ readonly entityId: Obj.ID;
66
+ };
67
+ /**
68
+ * The discriminated item flowing through the shared email tail. `insert` is a new message; `retag`/
69
+ * `delete` act on existing feed messages by `entityId`. All converge on the single {@link toCommitUnit}
70
+ * → `Cursor.commit`.
71
+ */
72
+ export type Change = Insert | Retag | Delete;
73
+ /** A decoded email attachment, ready for {@link processAttachments} to turn into a Blob. */
74
+ export type Attachment = {
75
+ readonly name?: string;
76
+ readonly mimeType?: string;
77
+ readonly size: number;
78
+ readonly bytes: Uint8Array;
79
+ /** The attachment's `Content-ID`, if any — matches a `cid:` reference in the message's HTML body. */
80
+ readonly contentId?: string;
81
+ };
82
+ /**
83
+ * Builds a Person (+ Organization link by domain) from the message sender and records it on the
84
+ * item as {@link Insert.contact} — the stage writes nothing itself; {@link toCommitUnit} defers
85
+ * the actual `db.add` to commit.
86
+ *
87
+ * Dedup is the space's shared identity index (see `getIdentityIndex`), refreshed once at the start
88
+ * of the run: it covers contacts committed before the run, contacts built earlier in it, and — since
89
+ * the index is shared per database — contacts a concurrently-syncing mailbox is building right now.
90
+ *
91
+ * Not every sender earns a record: {@link senderSignals} feeds the allow-list in
92
+ * `shouldExtractContact`, so bulk and automated mail no longer fills the space with contacts nobody
93
+ * will look at. NOTE: no provider records an outbound signal yet, so in practice the allow-list is
94
+ * "the domain matches a known Organization" — stricter than the policy intends. Extracting the
95
+ * recipients of sent mail is what closes that gap.
96
+ */
97
+ export declare const extractContacts: () => Stage.Stage<Change, Change, never, Database.Service>;
98
+ /**
99
+ * Turns each of the item's `attachments` into a Blob object (via the database's configured storage
100
+ * backend — edge in Composer) and adds a {@link Message.Attachment} pointing at it to the message.
101
+ * {@link toCommitUnit} finds these blobs again via each attachment's ref (inlined, since the
102
+ * blob isn't attached to a database yet) to defer their feed append to commit. A no-op when the item
103
+ * has no attachments.
104
+ *
105
+ * One bad or oversized attachment must not fail the whole message: each `Blob.fromBytes` is caught
106
+ * and logged individually, so a rejected attachment is simply dropped from the message.
107
+ */
108
+ export declare const processAttachments: () => Stage.Stage<Change, Change, never, Database.Service>;
109
+ /**
110
+ * Queries the parent container's already-sent drafts once per sync run and pools them by the provider
111
+ * message id captured at send time (`properties.sentMessageId`) — the key {@link reconcileDrafts}
112
+ * matches each incoming message's `foreignId` against. Doing this once (rather than per item) keeps
113
+ * reconciliation off the database as messages stream through. Unsent drafts (no `sentMessageId`) are
114
+ * never pooled and thus never eligible for removal.
115
+ *
116
+ * `parent` is the local root object drafts are scoped to (e.g. a Mailbox) — only its URI is read, so
117
+ * any object works.
118
+ */
119
+ export declare const queryDraftPool: (parent: Obj.Unknown) => Effect.Effect<Map<string, Message.Message[]>, never, Database.Service>;
120
+ /**
121
+ * Records the local drafts each incoming message supersedes — matched by the provider message id the
122
+ * draft captured at send time (`properties.sentMessageId`), which equals the synced message's
123
+ * `foreignId` — from a pool built once per run and keyed by that id. Writes nothing itself;
124
+ * {@link toCommitUnit} defers the `db.remove` to commit, so the canonical message is appended
125
+ * to the feed before its now-redundant drafts are deleted. A stage factory: pass the run's draft
126
+ * pool once.
127
+ *
128
+ * The pool is queried a single time by the caller (per sync operation), so this stage never touches
129
+ * the database or feed per item.
130
+ */
131
+ export declare const reconcileDrafts: (draftPool: ReadonlyMap<string, readonly Message.Message[]>) => Stage.Stage<Change, Change, never, never>;
132
+ /**
133
+ * Terminal stage converting a run's {@link Change} into the generic `Cursor.CommitUnit` the commit sink
134
+ * consumes. For an `insert`: `message.attachments` (populated by {@link processAttachments}) becomes a
135
+ * feed-append commit effect for the referenced blobs, `mapped.contact` (from {@link extractContacts})
136
+ * becomes a `db.add` commit effect, and `mapped.supersededDrafts` (from {@link reconcileDrafts})
137
+ * becomes a `db.remove` commit effect. Every other email stage is Change → Change and composes in
138
+ * any order; this is the one stage that must run last.
139
+ *
140
+ * Tagging is email-specific (`Cursor.CommitUnit` carries no tag data), so it's applied here rather
141
+ * than by the generic commit sink: `tagIndex` is optional (omit for providers/tests that don't tag),
142
+ * and every tagged unit attaches the *same* `applyTags` closure (keyed by `mapped.tagUris` recorded in
143
+ * a run-scoped map, read back by object identity) so the commit's identity-based batching applies
144
+ * every tag pair for the page in one `Tagging.setBatch` call rather than one per message.
145
+ */
146
+ export declare const toCommitUnit: (options?: {
147
+ readonly tagIndex?: TagIndex.TagIndex;
148
+ }) => Stage.Stage<Change, Cursor.CommitUnit, never, Cursor.Service>;
149
+ //# sourceMappingURL=EmailStage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailStage.d.ts","sourceRoot":"","sources":["../../../../src/stages/EmailStage.ts"],"names":[],"mappings":"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"}
@@ -9,5 +9,5 @@ export type FactUnit = {
9
9
  /** Monotonic cursor key. */
10
10
  readonly key: number;
11
11
  };
12
- export declare const extractFactsUnitStage: (extract: FactExtractor) => Stage.Stage<Message.Message, FactUnit>;
12
+ export declare const extractFactsUnitStage: (extract: FactExtractor, concurrency?: number) => Stage.Stage<Message.Message, FactUnit>;
13
13
  //# sourceMappingURL=extract-facts-commit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-facts-commit.d.ts","sourceRoot":"","sources":["../../../../src/stages/extract-facts-commit.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAOtC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AAI9E,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4BAA4B;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAMF,eAAO,MAAM,qBAAqB,YAAa,aAAa,KAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAOjG,CAAC"}
1
+ {"version":3,"file":"extract-facts-commit.d.ts","sourceRoot":"","sources":["../../../../src/stages/extract-facts-commit.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAOtC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AAI9E,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4BAA4B;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AASF,eAAO,MAAM,qBAAqB,YACvB,aAAa,2BAErB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAUrC,CAAC"}
@@ -5,4 +5,6 @@ export * from './extract-facts-commit';
5
5
  export * from './facts';
6
6
  export * from './stats';
7
7
  export * from './summarize';
8
+ export * from './tag';
9
+ export * as EmailStage from './EmailStage';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/stages/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/stages/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,OAAO,CAAC;AAEtB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC"}
@@ -0,0 +1,38 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { AiService } from '@dxos/ai';
3
+ import { Message } from '@dxos/types';
4
+ import { type ModelPolicy } from '../model-policy';
5
+ export type TagResult = {
6
+ readonly tags: string[];
7
+ readonly spam: boolean;
8
+ /** No-action automated mail (receipts, logins, confirmations) — distinct from spam and from action-required mail. */
9
+ readonly bulk: boolean;
10
+ };
11
+ /**
12
+ * Deterministic bulk / no-action signal. Returns `'bulk'` for mailing-list / automated mail —
13
+ * **any unsubscribe affordance** (a `List-Unsubscribe` header or an unsubscribe link in the body) is
14
+ * the top-priority give-away (RFC 2369), followed by receipts / logins / order confirmations /
15
+ * newsletters and no-reply role senders. Returns `'action'` when the mail requires a response
16
+ * (invoice / payment request) and has no unsubscribe affordance; `'unknown'` otherwise. Pure.
17
+ */
18
+ export declare const classifyBulk: (input: {
19
+ readonly subject?: string;
20
+ readonly senderEmail?: string;
21
+ /** `List-Unsubscribe` header value, when present (real synced mail). */
22
+ readonly listUnsubscribe?: string;
23
+ /** Message body text, scanned for an unsubscribe link when the header is absent (e.g. fixtures). */
24
+ readonly bodyText?: string;
25
+ }) => 'bulk' | 'action' | 'unknown';
26
+ /** Folds the deterministic bulk classification into a tag set: adds `bulk` for no-action mail, strips it for action-required mail. Pure. */
27
+ export declare const applyBulkTag: (tags: readonly string[], classification: 'bulk' | 'action' | 'unknown') => string[];
28
+ /** Parses the tag response leniently (models wrap JSON in prose); infers/dedups the spam tag. Pure. */
29
+ export declare const parseTagResult: (raw: string) => TagResult;
30
+ /**
31
+ * Tags a single message (topic tags + spam) via the AI service. The model comes from the `tag` stage
32
+ * of the model policy (overridable per run). Degrades to no tags on timeout/error so one bad message
33
+ * never fails the pipeline.
34
+ */
35
+ export declare const tagMessage: (message: Message.Message, options?: {
36
+ readonly policy?: ModelPolicy;
37
+ }) => Effect.Effect<TagResult, never, AiService.AiService>;
38
+ //# sourceMappingURL=tag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../../../../src/stages/tag.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,iBAAiB,CAAC;AAMjE,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,qHAAqH;IACrH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AA8BF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,UAAW;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,KAAG,MAAM,GAAG,QAAQ,GAAG,SAevB,CAAC;AAEF,4IAA4I;AAC5I,eAAO,MAAM,YAAY,SAAU,SAAS,MAAM,EAAE,kBAAkB,MAAM,GAAG,QAAQ,GAAG,SAAS,KAAG,MAAM,EAQ3G,CAAC;AAEF,uGAAuG;AACvG,eAAO,MAAM,cAAc,QAAS,MAAM,KAAG,SAmB5C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,YACZ,OAAO,CAAC,OAAO,YACf;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KACzC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,SAAS,CAwBlD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tag.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.test.d.ts","sourceRoot":"","sources":["../../../../src/stages/tag.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { type Message } from '@dxos/types';
2
+ import { type Summarizer, type TopicDraft, type TopicOptions } from './corpus';
3
+ import { type TagResult } from './stages/tag';
4
+ /** Per-message tag output — applied to the mailbox tag index by the caller. */
5
+ export type MessageTags = {
6
+ readonly messageId: string;
7
+ readonly tags: readonly string[];
8
+ readonly spam: boolean;
9
+ };
10
+ export type TopicsPipelineInput = {
11
+ readonly messages: readonly Message.Message[];
12
+ /** Mailbox owner email — steers thread-state inference in `buildThreads`. */
13
+ readonly ownerEmail: string;
14
+ /** Wall-clock ISO timestamp for `buildThreads` idle/state inference (injected — no `Date.now()` here). */
15
+ readonly now: string;
16
+ /** Cap messages tagged this run (resumable-lite); undefined → all. */
17
+ readonly limit?: number;
18
+ /** Skip a message already tagged by a previous run (resume). */
19
+ readonly skipMessage?: (messageId: string) => boolean;
20
+ /** Skip a topic already materialized by a previous run (dedupe on re-run). */
21
+ readonly skipTopic?: (label: string) => boolean;
22
+ /**
23
+ * Keep a clustered topic only when this returns true (applied before summarization). Used to create
24
+ * topics only for senders the user has a relationship with (an existing `Person` record); undefined
25
+ * keeps every topic.
26
+ */
27
+ readonly keepTopic?: (draft: TopicDraft) => boolean;
28
+ readonly topicOptions?: TopicOptions;
29
+ /** Progress hook: phase `'tag'` (per message) or `'topic'` (per summarized topic). */
30
+ readonly onProgress?: (phase: 'tag' | 'topic', current: number, total: number) => void;
31
+ /** Cooperative cancellation — checked between messages; aborting stops after the current item. */
32
+ readonly signal?: AbortSignal;
33
+ };
34
+ /** Effectful dependencies, injected so the orchestration stays pure and testable. */
35
+ export type TopicsPipelineDeps = {
36
+ readonly tag: (message: Message.Message) => Promise<TagResult>;
37
+ readonly summarize: Summarizer;
38
+ };
39
+ export type TopicsPipelineResult = {
40
+ readonly messageTags: readonly MessageTags[];
41
+ /**
42
+ * Summarized topic drafts (plain values, not ECHO objects — excludes any skipped by `skipTopic` /
43
+ * dropped by `keepTopic`). The caller decides what to do with them: materialize `Topic` objects, or
44
+ * write them to `Mailbox.topicSuggestions` for the user to curate.
45
+ */
46
+ readonly topicDrafts: readonly TopicDraft[];
47
+ };
48
+ /**
49
+ * Runs the topics pipeline: tag (bounded/resumable) → `buildThreads` → `clusterThreads` →
50
+ * `summarizeTopics`. Returns per-message tags and the summarized topic drafts for the caller to
51
+ * materialize or suggest. Tagging failures degrade per message (the injected `tag` should not
52
+ * reject); clustering is deterministic.
53
+ */
54
+ export declare const runTopicsPipeline: (input: TopicsPipelineInput, deps: TopicsPipelineDeps) => Promise<TopicsPipelineResult>;
55
+ //# sourceMappingURL=topics-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topics-pipeline.d.ts","sourceRoot":"","sources":["../../../src/topics-pipeline.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAkB,MAAM,UAAU,CAAC;AAE/F,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAS9C,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9C,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0GAA0G;IAC1G,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,sFAAsF;IACtF,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvF,kGAAkG;IAClG,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/D,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,WAAW,EAAE,SAAS,WAAW,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,UACrB,mBAAmB,QACpB,kBAAkB,KACvB,OAAO,CAAC,oBAAoB,CA6B9B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=topics-pipeline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topics-pipeline.test.d.ts","sourceRoot":"","sources":["../../../src/topics-pipeline.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,2 @@
1
1
  export * from './Thread';
2
- export * from './Topic';
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/types/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/types/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC"}