@dxos/pipeline-email 0.0.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 (147) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +15 -0
  3. package/dist/lib/neutral/index.mjs +650 -0
  4. package/dist/lib/neutral/index.mjs.map +7 -0
  5. package/dist/lib/neutral/meta.json +1 -0
  6. package/dist/lib/neutral/testing/index.mjs +48 -0
  7. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  8. package/dist/types/src/corpus/digest.d.ts +50 -0
  9. package/dist/types/src/corpus/digest.d.ts.map +1 -0
  10. package/dist/types/src/corpus/digest.test.d.ts +2 -0
  11. package/dist/types/src/corpus/digest.test.d.ts.map +1 -0
  12. package/dist/types/src/corpus/index.d.ts +6 -0
  13. package/dist/types/src/corpus/index.d.ts.map +1 -0
  14. package/dist/types/src/corpus/ledger.d.ts +33 -0
  15. package/dist/types/src/corpus/ledger.d.ts.map +1 -0
  16. package/dist/types/src/corpus/ledger.test.d.ts +2 -0
  17. package/dist/types/src/corpus/ledger.test.d.ts.map +1 -0
  18. package/dist/types/src/corpus/prompts.d.ts +21 -0
  19. package/dist/types/src/corpus/prompts.d.ts.map +1 -0
  20. package/dist/types/src/corpus/prompts.test.d.ts +2 -0
  21. package/dist/types/src/corpus/prompts.test.d.ts.map +1 -0
  22. package/dist/types/src/corpus/rollups.d.ts +21 -0
  23. package/dist/types/src/corpus/rollups.d.ts.map +1 -0
  24. package/dist/types/src/corpus/rollups.test.d.ts +2 -0
  25. package/dist/types/src/corpus/rollups.test.d.ts.map +1 -0
  26. package/dist/types/src/corpus/topics.d.ts +46 -0
  27. package/dist/types/src/corpus/topics.d.ts.map +1 -0
  28. package/dist/types/src/corpus/topics.test.d.ts +2 -0
  29. package/dist/types/src/corpus/topics.test.d.ts.map +1 -0
  30. package/dist/types/src/fact-pipeline.d.ts +26 -0
  31. package/dist/types/src/fact-pipeline.d.ts.map +1 -0
  32. package/dist/types/src/fact-pipeline.test.d.ts +2 -0
  33. package/dist/types/src/fact-pipeline.test.d.ts.map +1 -0
  34. package/dist/types/src/index.d.ts +6 -0
  35. package/dist/types/src/index.d.ts.map +1 -0
  36. package/dist/types/src/internal/fact-index.d.ts +8 -0
  37. package/dist/types/src/internal/fact-index.d.ts.map +1 -0
  38. package/dist/types/src/internal/fact-index.test.d.ts +2 -0
  39. package/dist/types/src/internal/fact-index.test.d.ts.map +1 -0
  40. package/dist/types/src/internal/threading.d.ts +4 -0
  41. package/dist/types/src/internal/threading.d.ts.map +1 -0
  42. package/dist/types/src/internal/threading.test.d.ts +2 -0
  43. package/dist/types/src/internal/threading.test.d.ts.map +1 -0
  44. package/dist/types/src/internal/threads.d.ts +9 -0
  45. package/dist/types/src/internal/threads.d.ts.map +1 -0
  46. package/dist/types/src/internal/threads.test.d.ts +2 -0
  47. package/dist/types/src/internal/threads.test.d.ts.map +1 -0
  48. package/dist/types/src/pipeline.d.ts +36 -0
  49. package/dist/types/src/pipeline.d.ts.map +1 -0
  50. package/dist/types/src/pipeline.test.d.ts +2 -0
  51. package/dist/types/src/pipeline.test.d.ts.map +1 -0
  52. package/dist/types/src/stages/context.d.ts +40 -0
  53. package/dist/types/src/stages/context.d.ts.map +1 -0
  54. package/dist/types/src/stages/extract-contacts.d.ts +6 -0
  55. package/dist/types/src/stages/extract-contacts.d.ts.map +1 -0
  56. package/dist/types/src/stages/extract-facts-commit.d.ts +13 -0
  57. package/dist/types/src/stages/extract-facts-commit.d.ts.map +1 -0
  58. package/dist/types/src/stages/extract-facts-commit.test.d.ts +2 -0
  59. package/dist/types/src/stages/extract-facts-commit.test.d.ts.map +1 -0
  60. package/dist/types/src/stages/extract-facts.d.ts +6 -0
  61. package/dist/types/src/stages/extract-facts.d.ts.map +1 -0
  62. package/dist/types/src/stages/extract-facts.test.d.ts +2 -0
  63. package/dist/types/src/stages/extract-facts.test.d.ts.map +1 -0
  64. package/dist/types/src/stages/facts.d.ts +7 -0
  65. package/dist/types/src/stages/facts.d.ts.map +1 -0
  66. package/dist/types/src/stages/facts.test.d.ts +2 -0
  67. package/dist/types/src/stages/facts.test.d.ts.map +1 -0
  68. package/dist/types/src/stages/index.d.ts +8 -0
  69. package/dist/types/src/stages/index.d.ts.map +1 -0
  70. package/dist/types/src/stages/stats.d.ts +6 -0
  71. package/dist/types/src/stages/stats.d.ts.map +1 -0
  72. package/dist/types/src/stages/stats.test.d.ts +2 -0
  73. package/dist/types/src/stages/stats.test.d.ts.map +1 -0
  74. package/dist/types/src/stages/summarize.d.ts +7 -0
  75. package/dist/types/src/stages/summarize.d.ts.map +1 -0
  76. package/dist/types/src/testing/email-extraction.bench.test.d.ts +2 -0
  77. package/dist/types/src/testing/email-extraction.bench.test.d.ts.map +1 -0
  78. package/dist/types/src/testing/email-fixtures.d.ts +25 -0
  79. package/dist/types/src/testing/email-fixtures.d.ts.map +1 -0
  80. package/dist/types/src/testing/email-pipeline.test.d.ts +2 -0
  81. package/dist/types/src/testing/email-pipeline.test.d.ts.map +1 -0
  82. package/dist/types/src/testing/index.d.ts +2 -0
  83. package/dist/types/src/testing/index.d.ts.map +1 -0
  84. package/dist/types/src/testing/parquet-email.test.d.ts +2 -0
  85. package/dist/types/src/testing/parquet-email.test.d.ts.map +1 -0
  86. package/dist/types/src/testing/parquet.d.ts +20 -0
  87. package/dist/types/src/testing/parquet.d.ts.map +1 -0
  88. package/dist/types/src/testing/parquet.test.d.ts +2 -0
  89. package/dist/types/src/testing/parquet.test.d.ts.map +1 -0
  90. package/dist/types/src/types/Thread.d.ts +18 -0
  91. package/dist/types/src/types/Thread.d.ts.map +1 -0
  92. package/dist/types/src/types/Thread.test.d.ts +2 -0
  93. package/dist/types/src/types/Thread.test.d.ts.map +1 -0
  94. package/dist/types/src/types/Topic.d.ts +12 -0
  95. package/dist/types/src/types/Topic.d.ts.map +1 -0
  96. package/dist/types/src/types/Topic.test.d.ts +2 -0
  97. package/dist/types/src/types/Topic.test.d.ts.map +1 -0
  98. package/dist/types/src/types/index.d.ts +3 -0
  99. package/dist/types/src/types/index.d.ts.map +1 -0
  100. package/dist/types/tsconfig.tsbuildinfo +1 -0
  101. package/package.json +58 -0
  102. package/src/corpus/digest.test.ts +78 -0
  103. package/src/corpus/digest.ts +92 -0
  104. package/src/corpus/index.ts +9 -0
  105. package/src/corpus/ledger.test.ts +85 -0
  106. package/src/corpus/ledger.ts +73 -0
  107. package/src/corpus/prompts.test.ts +24 -0
  108. package/src/corpus/prompts.ts +43 -0
  109. package/src/corpus/rollups.test.ts +50 -0
  110. package/src/corpus/rollups.ts +65 -0
  111. package/src/corpus/topics.test.ts +90 -0
  112. package/src/corpus/topics.ts +211 -0
  113. package/src/fact-pipeline.test.ts +63 -0
  114. package/src/fact-pipeline.ts +51 -0
  115. package/src/index.ts +9 -0
  116. package/src/internal/fact-index.test.ts +54 -0
  117. package/src/internal/fact-index.ts +43 -0
  118. package/src/internal/threading.test.ts +28 -0
  119. package/src/internal/threading.ts +27 -0
  120. package/src/internal/threads.test.ts +76 -0
  121. package/src/internal/threads.ts +73 -0
  122. package/src/pipeline.test.ts +74 -0
  123. package/src/pipeline.ts +76 -0
  124. package/src/stages/context.ts +32 -0
  125. package/src/stages/extract-contacts.ts +25 -0
  126. package/src/stages/extract-facts-commit.test.ts +75 -0
  127. package/src/stages/extract-facts-commit.ts +44 -0
  128. package/src/stages/extract-facts.test.ts +74 -0
  129. package/src/stages/extract-facts.ts +26 -0
  130. package/src/stages/facts.test.ts +40 -0
  131. package/src/stages/facts.ts +37 -0
  132. package/src/stages/index.ts +11 -0
  133. package/src/stages/stats.test.ts +38 -0
  134. package/src/stages/stats.ts +35 -0
  135. package/src/stages/summarize.ts +76 -0
  136. package/src/testing/email-extraction.bench.test.ts +117 -0
  137. package/src/testing/email-fixtures.ts +62 -0
  138. package/src/testing/email-pipeline.test.ts +483 -0
  139. package/src/testing/index.ts +8 -0
  140. package/src/testing/parquet-email.test.ts +114 -0
  141. package/src/testing/parquet.test.ts +73 -0
  142. package/src/testing/parquet.ts +82 -0
  143. package/src/types/Thread.test.ts +29 -0
  144. package/src/types/Thread.ts +26 -0
  145. package/src/types/Topic.test.ts +25 -0
  146. package/src/types/Topic.ts +20 -0
  147. package/src/types/index.ts +6 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/corpus/topics.ts", "../../../src/types/Thread.ts", "../../../src/types/Topic.ts", "../../../src/corpus/prompts.ts", "../../../src/corpus/ledger.ts", "../../../src/internal/threading.ts", "../../../src/corpus/rollups.ts", "../../../src/corpus/digest.ts", "../../../src/fact-pipeline.ts", "../../../src/stages/context.ts", "../../../src/stages/extract-contacts.ts", "../../../src/stages/extract-facts.ts", "../../../src/stages/extract-facts-commit.ts", "../../../src/stages/facts.ts", "../../../src/stages/stats.ts", "../../../src/stages/summarize.ts", "../../../src/pipeline.ts", "../../../src/internal/threads.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Obj } from '@dxos/echo';\n\nimport { type Thread, Topic } from '../types';\nimport { DEFAULT_EMAIL_PROMPTS, type EmailPrompts, type Summarizer, mergePrompts } from './prompts';\n\n// Subject tokens that carry no topical signal; excluded from signatures and keywords.\nconst DEFAULT_STOPWORDS: readonly string[] = [\n 'the',\n 'and',\n 'for',\n 'with',\n 'from',\n 'about',\n 'this',\n 'that',\n 'your',\n 'our',\n 'are',\n 'was',\n 'will',\n 'has',\n 'have',\n 'not',\n 'you',\n 'all',\n 'any',\n 'can',\n 'per',\n 'new',\n];\n\n/**\n * Tunable knobs for the deterministic shared-signal clustering (spec §10.3: agglomeration over\n * participant/keyword overlap; embedding/LLM grouping deferred). All values have defaults so\n * callers can adjust the algorithm without forking it.\n */\nexport type TopicOptions = {\n /** Minimum Jaccard similarity between subject-token signatures that joins a topic. */\n readonly similarityThreshold?: number;\n /** Shared-participant count that joins a topic regardless of token overlap. */\n readonly minSharedParticipants?: number;\n /** Subject tokens shorter than this are ignored. */\n readonly minTokenLength?: number;\n /** Tokens excluded from signatures and keywords. */\n readonly stopwords?: readonly string[];\n /** Keywords retained per topic (most frequent first). */\n readonly maxKeywords?: number;\n};\n\nexport const DEFAULT_TOPIC_OPTIONS: Required<TopicOptions> = {\n similarityThreshold: 0.25,\n minSharedParticipants: 2,\n minTokenLength: 3,\n stopwords: DEFAULT_STOPWORDS,\n maxKeywords: 5,\n};\n\n/** A topic before ECHO materialization: plain values so LLM enrichment can rewrite them freely. */\nexport type TopicDraft = {\n readonly label: string;\n readonly summary: string;\n readonly threadIds: readonly string[];\n readonly participants: readonly string[];\n readonly keywords: readonly string[];\n};\n\ntype Signature = {\n readonly thread: Thread;\n readonly tokens: Set<string>;\n readonly participants: Set<string>;\n};\n\nconst tokenize = (subject: string, options: Required<TopicOptions>): Set<string> =>\n new Set(\n subject\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length >= options.minTokenLength && !options.stopwords.includes(token)),\n );\n\nconst jaccard = (left: Set<string>, right: Set<string>): number => {\n if (left.size === 0 || right.size === 0) {\n return 0;\n }\n let shared = 0;\n for (const token of left) {\n if (right.has(token)) {\n shared += 1;\n }\n }\n return shared / (left.size + right.size - shared);\n};\n\nconst sharedCount = (left: Set<string>, right: Set<string>): number => {\n let shared = 0;\n for (const value of left) {\n if (right.has(value)) {\n shared += 1;\n }\n }\n return shared;\n};\n\n/**\n * Greedy agglomerative clustering of threads into topic drafts. Deterministic (Compute mode): a\n * thread joins the first cluster whose aggregate signature overlaps enough — by subject-token\n * Jaccard similarity or by shared participants — else starts its own. Bounded by O(threads ×\n * clusters); no LLM involved, so results are stable and unit-testable.\n */\nexport const clusterThreads = (threads: readonly Thread[], options?: TopicOptions): TopicDraft[] => {\n const opts = { ...DEFAULT_TOPIC_OPTIONS, ...options };\n const clusters: { members: Signature[]; tokens: Set<string>; participants: Set<string> }[] = [];\n\n for (const thread of threads) {\n const signature: Signature = {\n thread,\n tokens: tokenize(thread.subject, opts),\n participants: new Set(thread.participants),\n };\n const home = clusters.find(\n (cluster) =>\n jaccard(cluster.tokens, signature.tokens) >= opts.similarityThreshold ||\n sharedCount(cluster.participants, signature.participants) >= opts.minSharedParticipants,\n );\n if (home) {\n home.members.push(signature);\n for (const token of signature.tokens) {\n home.tokens.add(token);\n }\n for (const participant of signature.participants) {\n home.participants.add(participant);\n }\n } else {\n clusters.push({\n members: [signature],\n tokens: new Set(signature.tokens),\n participants: new Set(signature.participants),\n });\n }\n }\n\n return clusters.map((cluster) => {\n // Rank keywords by how many member threads mention them, then alphabetically for determinism.\n const counts = new Map<string, number>();\n for (const member of cluster.members) {\n for (const token of member.tokens) {\n counts.set(token, (counts.get(token) ?? 0) + 1);\n }\n }\n const keywords = [...counts.entries()]\n .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))\n .slice(0, opts.maxKeywords)\n .map(([token]) => token);\n\n const summaries = cluster.members.flatMap((member) =>\n member.thread.summary.length > 0 ? [member.thread.summary] : [],\n );\n\n // Label from top keywords; a keyword-less cluster (e.g. blank subjects) falls back through the\n // first thread's subject to its threadId so the label is never empty.\n const first = cluster.members[0].thread;\n return {\n label: keywords.length > 0 ? keywords.slice(0, 3).join(' ') : first.subject || first.threadId,\n summary: summaries.join('\\n'),\n threadIds: cluster.members.map((member) => member.thread.threadId),\n participants: [...cluster.participants].sort(),\n keywords,\n };\n });\n};\n\n/**\n * Optionally replace each draft's deterministic summary with an LLM one, using the editable\n * `topicSummary` prompt. Degrades per-topic: a failed call keeps that draft's deterministic summary\n * (advisory enrichment must not fail the corpus pass).\n */\nexport const summarizeTopics = async (\n drafts: readonly TopicDraft[],\n summarize: Summarizer,\n options?: { readonly prompts?: Partial<EmailPrompts> },\n): Promise<TopicDraft[]> => {\n const prompts = options?.prompts ? mergePrompts(options.prompts) : DEFAULT_EMAIL_PROMPTS;\n return Promise.all(\n drafts.map(async (draft) => {\n try {\n const summary = await summarize(\n [prompts.topicSummary, `Topic: ${draft.label}`, `Threads:\\n${draft.summary}`].join('\\n\\n'),\n );\n return summary.trim().length > 0 ? { ...draft, summary: summary.trim() } : draft;\n } catch {\n return draft;\n }\n }),\n );\n};\n\n/** Materialize drafts as canonical Topic ECHO objects (done last; drafts stay freely mutable). */\nexport const materializeTopics = (drafts: readonly TopicDraft[]): Topic[] =>\n drafts.map((draft) =>\n Obj.make(Topic, {\n label: draft.label,\n summary: draft.summary,\n threadIds: [...draft.threadIds],\n participants: [...draft.participants],\n keywords: [...draft.keywords],\n }),\n );\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { DXN, Type } from '@dxos/echo';\n\nexport const ThreadState = Schema.Literal('awaiting-mine', 'awaiting-theirs', 'resolved', 'stalled');\nexport type ThreadState = Schema.Schema.Type<typeof ThreadState>;\n\n// A canonical (spec §4) conversation aggregate: messages sharing a derived threadId, with a rolling\n// summary, a coarse state, and consolidated open questions / action items. Authoritative in ECHO;\n// facts are advisory evidence, not the source of this object's state.\nexport class Thread extends Type.makeObject<Thread>(DXN.make('org.dxos.type.emailThread', '0.1.0'))(\n Schema.Struct({\n threadId: Schema.String,\n subject: Schema.String,\n summary: Schema.String,\n state: ThreadState,\n participants: Schema.Array(Schema.String),\n messageIds: Schema.Array(Schema.String),\n openQuestions: Schema.Array(Schema.String),\n actionItems: Schema.Array(Schema.String),\n }),\n) {}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { DXN, Type } from '@dxos/echo';\n\n// A canonical (spec §4) meta-thread: a cluster of related conversations discovered at the corpus\n// layer, with an optional LLM label/summary. Authoritative in ECHO; string-keyed thread references\n// (like Thread.messageIds) until the entity layer firms up to DXN refs.\nexport class Topic extends Type.makeObject<Topic>(DXN.make('org.dxos.type.emailTopic', '0.1.0'))(\n Schema.Struct({\n label: Schema.String,\n summary: Schema.String,\n threadIds: Schema.Array(Schema.String),\n participants: Schema.Array(Schema.String),\n keywords: Schema.Array(Schema.String),\n }),\n) {}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { trim } from '@dxos/util';\n\n/**\n * User-editable prompts for the LLM-assisted corpus algorithms. Every slot has a default; callers\n * override any subset via {@link mergePrompts}, so the discovery algorithms stay configurable\n * without forking the pipeline. (Extraction rules are configured separately, via pipeline-rdf\n * `ExtractOptions.rules`.)\n */\nexport type EmailPrompts = {\n /** Summarize one topic (cluster of threads); receives the topic label + per-thread summaries. */\n readonly topicSummary: string;\n /** Narrate a corpus digest; receives the deterministic digest skeleton. */\n readonly digest: string;\n};\n\nexport const DEFAULT_EMAIL_PROMPTS: EmailPrompts = {\n topicSummary: trim`\n Summarize the following email conversation topic in 1-2 sentences.\n Focus on what the threads have in common and any outcome or open question.\n Respond with ONLY the summary text.\n `,\n digest: trim`\n Write a short briefing (3-5 sentences) from the following inbox digest data.\n Lead with what needs attention (stalled threads, due commitments), then notable topics.\n Respond with ONLY the briefing text.\n `,\n};\n\n/** Overlay caller overrides onto the defaults; absent slots keep their default. */\nexport const mergePrompts = (overrides?: Partial<EmailPrompts>): EmailPrompts => ({\n ...DEFAULT_EMAIL_PROMPTS,\n ...overrides,\n});\n\n/**\n * LLM call boundary used by the corpus algorithms: the harness owns model/runtime plumbing and hands\n * modules a plain async closure (same pattern as `FactIndexer`), so algorithm Effects stay R = never.\n */\nexport type Summarizer = (prompt: string) => Promise<string>;\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { type FactStoreApi, type RDF, type SemanticIndexError } from '@dxos/pipeline-rdf';\n\n/**\n * Predicates treated as commitments (spec §3③ commitment ledger). Editable: extraction phrasing\n * varies by corpus and extraction rules, so callers tune this list rather than the query logic.\n */\nexport const DEFAULT_COMMITMENT_PREDICATES: readonly string[] = ['owes', 'will send', 'will provide', 'must', 'due'];\n\nexport type LedgerOptions = {\n /** Predicates that mark a fact as a commitment. */\n readonly predicates?: readonly string[];\n};\n\n/** One row of the commitment ledger, traceable to its supporting fact. */\nexport type Commitment = {\n /** Who owes it (fact subject label). */\n readonly who: string;\n /** What is owed (fact object label or literal). */\n readonly what: string;\n /** Deadline (assertion validTo), when stated. */\n readonly dueBy?: string;\n /** Model confidence carried from the fact's factuality. */\n readonly confidence?: number;\n readonly factId: string;\n /** Source message (fact attribution). */\n readonly source: string;\n};\n\nconst termLabel = (term: RDF.Assertion['subject']): string =>\n 'entity' in term ? (term.label ?? term.entity) : term.literal;\n\nconst toCommitment = (fact: RDF.Fact): Commitment => ({\n who: termLabel(fact.assertion.subject),\n what: termLabel(fact.assertion.object),\n ...(fact.assertion.validTo ? { dueBy: fact.assertion.validTo } : {}),\n ...(fact.factuality.confidence !== undefined ? { confidence: fact.factuality.confidence } : {}),\n factId: fact.id,\n source: fact.attribution.source,\n});\n\n/**\n * Query the fact store for outstanding commitments — the spec's cross-thread \"owe / owed\" ledger.\n * Uses the structured query path (one `query({ predicate })` per commitment predicate, compiled to\n * SPARQL on the sqlite backend), which works on both store backends; rows are advisory evidence\n * (§4 canonicality) and each carries its fact id + source message for grounding.\n */\nexport const commitmentLedger = (\n store: FactStoreApi,\n options?: LedgerOptions,\n): Effect.Effect<Commitment[], SemanticIndexError> =>\n Effect.forEach(options?.predicates ?? DEFAULT_COMMITMENT_PREDICATES, (predicate) => store.query({ predicate })).pipe(\n Effect.map((results) => {\n // A fact can match several predicate probes; keep the first occurrence.\n const seen = new Set<string>();\n const commitments: Commitment[] = [];\n for (const fact of results.flat()) {\n if (!seen.has(fact.id)) {\n seen.add(fact.id);\n commitments.push(toCommitment(fact));\n }\n }\n // Deadlines first (soonest first), then the rest in stable fact order.\n return commitments.sort((a, b) =>\n a.dueBy && b.dueBy ? a.dueBy.localeCompare(b.dueBy) : a.dueBy ? -1 : b.dueBy ? 1 : 0,\n );\n }),\n );\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { Message } from '@dxos/types';\n\n// Leading reply/forward markers, stripped repeatedly (\"Fw: Re:\" → \"\").\nconst PREFIX = /^\\s*(re|fwd?|fw)\\s*:\\s*/i;\n\n// Canonical subject for threading: drop reply/forward prefixes, collapse internal whitespace,\n// lowercase. Two messages with the same normalized subject belong to the same thread.\nexport const normalizeSubject = (subject: string): string => {\n let value = subject;\n while (PREFIX.test(value)) {\n value = value.replace(PREFIX, '');\n }\n return value.replace(/\\s+/g, ' ').trim().toLowerCase();\n};\n\n// Derive a thread id from the subject. The Enron dataset carries no References/In-Reply-To headers,\n// so the normalized subject is the available grouping signal; blank subjects share a `no-subject`\n// bucket rather than each forming a singleton thread.\nexport const deriveThreadId = (message: Message.Message): string => {\n const subject = message.properties?.subject;\n const normalized = typeof subject === 'string' ? normalizeSubject(subject) : '';\n return normalized.length > 0 ? normalized : 'no-subject';\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { Message } from '@dxos/types';\n\nimport { deriveThreadId } from '../internal/threading';\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/**\n * Per-sender relationship aggregate (spec §3③ relationship rollups). Computed values, not ECHO\n * mutations: `Person` is a closed, canonical schema the pipeline must not write onto, so rollups are\n * consumed by the digest / callers (join back to a Person via the lowercased email and the\n * fact-index reconciliation table); materialization waits for a proper schema home.\n */\nexport type RelationshipRollup = {\n /** Lowercased sender address (email addresses are case-insensitive). */\n readonly email: string;\n readonly messageCount: number;\n readonly firstContact: string;\n readonly lastContact: string;\n /** Mean days between messages; absent below two messages. */\n readonly cadenceDays?: number;\n /** Threads this sender participated in (send-side), in first-seen order. */\n readonly threadIds: readonly string[];\n};\n\n/** Aggregate messages into per-sender rollups, most active sender first. Deterministic (Compute mode). */\nexport const buildRollups = (messages: readonly Message.Message[]): RelationshipRollup[] => {\n const bySender = new Map<string, { created: string[]; threadIds: string[] }>();\n for (const message of messages) {\n const email = message.sender.email?.toLowerCase();\n if (!email) {\n continue;\n }\n let entry = bySender.get(email);\n if (!entry) {\n entry = { created: [], threadIds: [] };\n bySender.set(email, entry);\n }\n entry.created.push(message.created);\n const threadId = deriveThreadId(message);\n if (!entry.threadIds.includes(threadId)) {\n entry.threadIds.push(threadId);\n }\n }\n\n const rollups: RelationshipRollup[] = [];\n for (const [email, entry] of bySender) {\n const ordered = [...entry.created].sort();\n const first = ordered[0];\n const last = ordered[ordered.length - 1];\n const spanMs = new Date(last).getTime() - new Date(first).getTime();\n rollups.push({\n email,\n messageCount: ordered.length,\n firstContact: first,\n lastContact: last,\n ...(ordered.length >= 2 ? { cadenceDays: spanMs / (ordered.length - 1) / MS_PER_DAY } : {}),\n threadIds: entry.threadIds,\n });\n }\n return rollups.sort((a, b) => b.messageCount - a.messageCount || a.email.localeCompare(b.email));\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Thread } from '../types';\nimport { type Commitment } from './ledger';\nimport { DEFAULT_EMAIL_PROMPTS, type EmailPrompts, type Summarizer, mergePrompts } from './prompts';\nimport { type RelationshipRollup } from './rollups';\nimport { type TopicDraft } from './topics';\n\n/**\n * Everything the corpus pass has computed; the digest is a Query-mode view over it (spec §3③\n * briefings) — assembled on demand, never materialized as ECHO.\n */\nexport type DigestInput = {\n readonly threads: readonly Thread[];\n readonly topics: readonly TopicDraft[];\n readonly commitments: readonly Commitment[];\n readonly rollups: readonly RelationshipRollup[];\n};\n\nexport type DigestOptions = {\n /** Timestamp the digest reports as its generation time (passed in for determinism). */\n readonly now: string;\n /** Topics listed in the skeleton (largest first). */\n readonly maxTopTopics?: number;\n};\n\n/** Deterministic digest skeleton; `narrative` stays empty until {@link narrateDigest} fills it. */\nexport type Digest = {\n readonly generatedAt: string;\n readonly threadCount: number;\n readonly topicCount: number;\n /** Threads needing attention. */\n readonly stalledThreads: readonly string[];\n readonly awaitingMine: readonly string[];\n /** Commitments carrying a deadline, soonest first. */\n readonly dueCommitments: readonly Commitment[];\n readonly topTopics: readonly { readonly label: string; readonly threadCount: number }[];\n /** LLM briefing text; empty when narration is off or degraded. */\n readonly narrative: string;\n};\n\n/** Assemble the deterministic digest skeleton from the corpus outputs (Compute mode; no LLM). */\nexport const buildDigest = (input: DigestInput, options: DigestOptions): Digest => ({\n generatedAt: options.now,\n threadCount: input.threads.length,\n topicCount: input.topics.length,\n stalledThreads: input.threads.filter((thread) => thread.state === 'stalled').map((thread) => thread.threadId),\n awaitingMine: input.threads.filter((thread) => thread.state === 'awaiting-mine').map((thread) => thread.threadId),\n dueCommitments: input.commitments\n .filter((commitment) => commitment.dueBy !== undefined)\n .sort((a, b) => (a.dueBy ?? '').localeCompare(b.dueBy ?? '')),\n topTopics: [...input.topics]\n .sort((a, b) => b.threadIds.length - a.threadIds.length || a.label.localeCompare(b.label))\n .slice(0, options.maxTopTopics ?? 5)\n .map((topic) => ({ label: topic.label, threadCount: topic.threadIds.length })),\n narrative: '',\n});\n\n/** Render the skeleton as plain text — the prompt payload for narration, and a fallback briefing. */\nexport const renderDigest = (digest: Digest): string =>\n [\n `Inbox digest (${digest.generatedAt})`,\n `Threads: ${digest.threadCount} Topics: ${digest.topicCount}`,\n `Awaiting my reply: ${digest.awaitingMine.join(', ') || 'none'}`,\n `Stalled: ${digest.stalledThreads.join(', ') || 'none'}`,\n `Due commitments: ${\n digest.dueCommitments\n .map((commitment) => `${commitment.who} → ${commitment.what} (${commitment.dueBy})`)\n .join('; ') || 'none'\n }`,\n `Top topics: ${digest.topTopics.map((topic) => `${topic.label} (${topic.threadCount})`).join('; ') || 'none'}`,\n ].join('\\n');\n\n/**\n * Optionally add an LLM briefing over the skeleton, using the editable `digest` prompt. Degrades to\n * the un-narrated digest on failure — the skeleton is always available.\n */\nexport const narrateDigest = async (\n digest: Digest,\n summarize: Summarizer,\n options?: { readonly prompts?: Partial<EmailPrompts> },\n): Promise<Digest> => {\n const prompts = options?.prompts ? mergePrompts(options.prompts) : DEFAULT_EMAIL_PROMPTS;\n try {\n const narrative = await summarize([prompts.digest, renderDigest(digest)].join('\\n\\n'));\n return { ...digest, narrative: narrative.trim() };\n } catch {\n return digest;\n }\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\n\nimport { type Database } from '@dxos/echo';\nimport { Pipeline } from '@dxos/pipeline';\nimport { Message } from '@dxos/types';\n\nimport { EmailPipelineCtx, type FactIndexer, type Stats, emptyStats, extractFactsStage, statsStage } from './stages';\n\nexport type EmailFactPipelineOptions = {\n /** ECHO space database, required by `EmailPipelineCtx` even though this assembly never writes to it. */\n readonly db: Database.Database;\n /** Persists one message's facts to the fact substrate (a closure over a `FactStore`). */\n readonly indexFacts: FactIndexer;\n};\n\nexport type EmailFactPipelineResult = {\n readonly messages: Message.Message[];\n readonly stats: Stats;\n};\n\nexport const EmailFactPipeline = {\n /**\n * Facts-only assembly of the email stages over a batch of messages: stats → extract-facts. Omits\n * summarize, extract-contacts, and {@link buildThreads} from {@link EmailPipeline}, so it needs no\n * `AiService` and never writes to `db`. The cursored `EnrichMailbox` operation (in plugin-inbox)\n * covers the same fact-extraction concern incrementally, via a sibling facts-returning stage\n * (`extractFactsUnitStage`) that commits pages atomically with a cursor advance instead of indexing\n * in-stage; this batch variant is for one-shot runs over an already-collected set of messages.\n */\n run: (\n messages: readonly Message.Message[],\n options: EmailFactPipelineOptions,\n ): Effect.Effect<EmailFactPipelineResult> =>\n Effect.gen(function* () {\n const stats = emptyStats();\n const collected: Message.Message[] = [];\n yield* Stream.fromIterable(messages).pipe(\n statsStage,\n extractFactsStage(options.indexFacts),\n Pipeline.run({ sink: (message) => Effect.sync(() => collected.push(message)) }),\n Effect.provideService(EmailPipelineCtx, { db: options.db, stats, summaries: [] }),\n );\n\n return { messages: collected, stats };\n }),\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Context from 'effect/Context';\n\nimport { type Database } from '@dxos/echo';\n\n/** Summary produced by the summarize stage. */\nexport type Summary = { readonly summary: string; readonly isSpam: boolean; readonly keywords: readonly string[] };\n\n/** Running tallies produced by the stats stage (mutable accumulator). */\nexport type Stats = { from: Map<string, number>; to: Map<string, number>; total: number; spam: number };\n\n/** Per-message summary result keyed by messageId (for a summary view). */\nexport type SummaryResult = { readonly summaries: ReadonlyArray<{ messageId: string; summary: Summary }> };\n\nexport const emptyStats = (): Stats => ({ from: new Map(), to: new Map(), total: 0, spam: 0 });\n\n/**\n * Shared context threaded through the email stages via Effect's Requirements channel. `db` is the\n * ECHO space database (browser-safe); `stats` and `summaries` are mutable accumulators read after\n * the run. Callers provide it once at the pipeline edge (`Effect.provide`).\n */\nexport class EmailPipelineCtx extends Context.Tag('@dxos/pipeline-email/EmailPipelineCtx')<\n EmailPipelineCtx,\n {\n readonly db: Database.Database;\n readonly stats: Stats;\n readonly summaries: Array<{ messageId: string; summary: Summary }>;\n }\n>() {}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { extractContact } from '@dxos/extractor-lib';\nimport { Stage } from '@dxos/pipeline';\nimport { Message } from '@dxos/types';\n\nimport { EmailPipelineCtx } from './context';\n\n/** Extract a Person (+ Organization) from the sender and persist to the ECHO space; pass message through. */\nexport const extractContactsStage: Stage.Stage<Message.Message, Message.Message, never, EmailPipelineCtx> = Stage.map(\n 'extract-contacts',\n (message) =>\n Effect.gen(function* () {\n const { db } = yield* EmailPipelineCtx;\n const result = yield* extractContact({ db, source: message });\n for (const object of result.created) {\n db.add(object);\n }\n return message;\n }),\n);\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Stage } from '@dxos/pipeline';\nimport { type RDF } from '@dxos/pipeline-rdf';\nimport { Message } from '@dxos/types';\n\n// Extract + persist one message's facts, returning them. The store/AI-bound work is a Promise closure\n// so the stage's Effect stays R = never (a failed extraction must not fail the pipeline run).\nexport type FactIndexer = (message: Message.Message) => Promise<RDF.Fact[]>;\n\n// Message-layer stage: index each message into the fact substrate, passing the Message through\n// unchanged. Extraction degrades to no facts on failure (advisory layer) but logs first, so the\n// degradation stays observable. A factory over `indexFacts` (like the pipeline's `logStage(label)`)\n// keeps the module decoupled from any test-level Context.\nexport const extractFactsStage = (indexFacts: FactIndexer): Stage.Stage<Message.Message, Message.Message> =>\n Stage.map('extract-facts', (message) =>\n Effect.tryPromise(() => indexFacts(message)).pipe(\n Effect.tapError((error) => Effect.logWarning('extract-facts failed; degrading to no facts', error)),\n Effect.orElse(() => Effect.succeed<RDF.Fact[]>([])),\n Effect.as(message),\n ),\n );\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Stage } from '@dxos/pipeline';\nimport { type RDF } from '@dxos/pipeline-rdf';\nimport { Message } from '@dxos/types';\n\nimport { messageSource } from './facts';\n\n// Extract one message's facts WITHOUT persisting them. Distinct from `FactIndexer`, whose closure\n// also writes to the store: here persistence is the sink's responsibility, so a page of facts can\n// commit atomically with the cursor advance. Same signature, different contract.\nexport type FactExtractor = (message: Message.Message) => Promise<RDF.Fact[]>;\n\n// Terminal unit for the cursored fact pipeline: extracted facts plus the keys the sink needs to\n// dedup and advance the cursor.\nexport type FactUnit = {\n readonly facts: RDF.Fact[];\n /** Stable per-message id (`messageSource`); the dedup foreign id. */\n readonly foreignId: string;\n /** Monotonic cursor key. */\n readonly key: number;\n};\n\n// Facts-returning variant of `extractFactsStage`: returns the extracted facts as a `FactUnit`\n// instead of persisting them in-stage, so the sink can write a page atomically with the cursor\n// advance. Extraction degrades to no facts on failure (advisory layer) but logs first, matching\n// `extractFactsStage`'s degradation, so the loss stays observable.\nexport const extractFactsUnitStage = (extract: FactExtractor): Stage.Stage<Message.Message, FactUnit> =>\n Stage.map('extract-facts-unit', (message) =>\n Effect.tryPromise(() => extract(message)).pipe(\n Effect.tapError((error) => Effect.logWarning('extract-facts-unit failed; degrading to no facts', error)),\n Effect.orElse(() => Effect.succeed<RDF.Fact[]>([])),\n Effect.map((facts): FactUnit => ({ facts, foreignId: messageSource(message), key: keyOf(message) })),\n ),\n );\n\n// NOTE(workaround): `message.created` is the incremental cursor key because ECHO's native feed\n// cursor is unimplemented (`Feed.cursor` is stubbed). Replace with the native queue sequence when\n// available.\nconst keyOf = (message: Message.Message): number => Date.parse(message.created);\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { type RDF } from '@dxos/pipeline-rdf';\nimport { Message } from '@dxos/types';\n\n// Email-specific extraction guidance appended after the pipeline-rdf DEFAULT_EXTRACTION_RULES.\n// Kept atomic so the two rule sets compose without forking the base prompt.\nexport const EMAIL_EXTRACTION_RULES: readonly string[] = [\n 'Treat a request or promise as a commitment: \"please send X by Friday\" => subject (the owner), predicate \"owes\"/\"will send\", object X, validTo the deadline.',\n 'A deadline or due date becomes validTo on the relevant assertion (ISO date when known).',\n 'Attribute first-person statements (\"I will…\", \"we can…\") to the message sender as subject.',\n];\n\nexport const EMAIL_EXTRACT_OPTIONS: RDF.ExtractOptions = {\n rules: EMAIL_EXTRACTION_RULES,\n};\n\n// Stable per-message identifier used as the fact `source` and the incremental cursor key. Prefer the\n// RFC message-id header (present in the Enron dataset); fall back to sender+timestamp so every message\n// has a deterministic source even when the header is absent.\nexport const messageSource = (message: Message.Message): string => {\n const messageId = message.properties?.messageId;\n if (typeof messageId === 'string' && messageId.length > 0) {\n return messageId;\n }\n\n return `${message.sender.email ?? 'unknown'}:${message.created}`;\n};\n\nexport const messageToDocument = (message: Message.Message): RDF.ExtractDocument => ({\n text: Message.extractText(message),\n source: messageSource(message),\n ...(message.sender.email ? { author: message.sender.email } : {}),\n date: message.created,\n});\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Stage } from '@dxos/pipeline';\nimport { Message } from '@dxos/types';\n\nimport { EmailPipelineCtx } from './context';\n\n/** Pure-JS running tallies (senders, recipients, spam); pass message through. */\nexport const statsStage: Stage.Stage<Message.Message, Message.Message, never, EmailPipelineCtx> = Stage.map(\n 'stats',\n (message) =>\n Effect.gen(function* () {\n const { stats } = yield* EmailPipelineCtx;\n stats.total += 1;\n const sender = message.sender.email;\n if (sender) {\n stats.from.set(sender, (stats.from.get(sender) ?? 0) + 1);\n }\n const recipients = message.properties?.to;\n if (Array.isArray(recipients)) {\n for (const recipient of recipients) {\n const address = String(recipient);\n stats.to.set(address, (stats.to.get(address) ?? 0) + 1);\n }\n }\n if (message.properties?.spam) {\n stats.spam += 1;\n }\n return message;\n }),\n);\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as LanguageModel from '@effect/ai/LanguageModel';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { AiService } from '@dxos/ai';\nimport { Stage } from '@dxos/pipeline';\nimport { ContentBlock, Message } from '@dxos/types';\nimport { trim } from '@dxos/util';\n\nimport { EmailPipelineCtx, type Summary } from './context';\n\nconst SUMMARIZE_MODEL = 'com.anthropic.model.claude-haiku-4-5.default';\n\nconst SUMMARIZE_PROMPT = trim`\n Summarize the following email in one sentence, decide whether it is spam, and list up to five keywords.\n Respond with ONLY a JSON object of the form {\"summary\": string, \"isSpam\": boolean, \"keywords\": string[]}.\n`;\n\n// Local models often wrap JSON in prose; extract the first object and coerce leniently.\nconst parseSummary = (raw: string): Summary => {\n const match = raw.match(/\\{[\\s\\S]*\\}/);\n if (!match) {\n return { summary: '', isSpam: false, keywords: [] };\n }\n try {\n const parsed = JSON.parse(match[0]);\n const keywords = parsed.keywords;\n return {\n summary: typeof parsed.summary === 'string' ? parsed.summary : '',\n isSpam: parsed.isSpam === true || parsed.spam === true || parsed.is_spam === true,\n keywords: Array.isArray(keywords) ? keywords.map((keyword: unknown) => String(keyword)) : [],\n };\n } catch {\n return { summary: '', isSpam: false, keywords: [] };\n }\n};\n\n/** Summarize via AiService/LanguageModel; append a summary block and record spam/keywords + result. */\nexport const summarizeStage: Stage.Stage<\n Message.Message,\n Message.Message,\n never,\n EmailPipelineCtx | AiService.AiService\n> = Stage.map('summarize', (message) =>\n Effect.gen(function* () {\n const ctx = yield* EmailPipelineCtx;\n const text = Message.extractText(message);\n // Resolve the LanguageModel layer locally from AiService so `LanguageModel` is discharged and the\n // stage's requirement correctly reduces to `EmailPipelineCtx | AiService.AiService`.\n // Bound the LLM call: `orElse` only recovers failures, so without a timeout a hung/slow provider\n // would block the stage indefinitely. On timeout the effect fails, then `orElse` degrades to ''.\n const raw = yield* LanguageModel.generateText({ prompt: `${SUMMARIZE_PROMPT}\\n\\n${text}` }).pipe(\n Effect.provide(AiService.model(SUMMARIZE_MODEL).pipe(Layer.orDie)),\n Effect.timeout('30 seconds'),\n Effect.map((response) => response.text),\n Effect.orElse(() => Effect.succeed('')),\n );\n const summary = parseSummary(raw);\n const messageId = String(message.properties?.messageId ?? message.id);\n ctx.summaries.push({ messageId, summary });\n const summaryBlock: ContentBlock.Text = { _tag: 'text', text: summary.summary };\n // Preserve the original id: `Message.make` mints a fresh one when omitted, which would break the\n // `message.id` fallback used for `messageId` downstream.\n return Message.make({\n id: message.id,\n created: message.created,\n sender: message.sender,\n blocks: [...message.blocks, summaryBlock],\n properties: { ...message.properties, spam: summary.isSpam, keywords: summary.keywords },\n });\n }),\n);\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\n\nimport { type AiService } from '@dxos/ai';\nimport { type Database } from '@dxos/echo';\nimport { Pipeline } from '@dxos/pipeline';\nimport { Message } from '@dxos/types';\n\nimport { buildThreads } from './internal/threads';\nimport {\n EmailPipelineCtx,\n type FactIndexer,\n type Stats,\n type Summary,\n emptyStats,\n extractContactsStage,\n extractFactsStage,\n statsStage,\n summarizeStage,\n} from './stages';\nimport { type Thread } from './types';\n\nexport type EmailPipelineOptions = {\n /** ECHO space database: extract-contacts persists Person/Organization, threads are added here. */\n readonly db: Database.Database;\n /** Persists one message's facts to the fact substrate (a closure over a `FactStore`). */\n readonly indexFacts: FactIndexer;\n /** The space owner's email, used to orient thread state (awaiting-mine / awaiting-theirs). */\n readonly ownerEmail: string;\n /** Wall clock used to derive thread staleness; injected so the assembly stays deterministic. */\n readonly now: string;\n};\n\nexport type EmailPipelineResult = {\n readonly messages: Message.Message[];\n readonly stats: Stats;\n readonly summaries: ReadonlyArray<{ messageId: string; summary: Summary }>;\n readonly threads: Thread[];\n};\n\nexport const EmailPipeline = {\n /**\n * Default assembly of the email stages over a batch of messages: summarize → extract-contacts →\n * stats → extract-facts stream through with back pressure, then {@link buildThreads} groups the\n * collected messages into canonical threads. Contacts and threads are persisted to `db`; the fact\n * substrate is populated via `indexFacts`. Requires an `AiService` (summarize); the shared\n * {@link EmailPipelineCtx} is constructed and provided internally.\n */\n run: (\n messages: readonly Message.Message[],\n options: EmailPipelineOptions,\n ): Effect.Effect<EmailPipelineResult, never, AiService.AiService> =>\n Effect.gen(function* () {\n const stats = emptyStats();\n const summaries: Array<{ messageId: string; summary: Summary }> = [];\n const collected: Message.Message[] = [];\n yield* Stream.fromIterable(messages).pipe(\n summarizeStage,\n extractContactsStage,\n statsStage,\n extractFactsStage(options.indexFacts),\n Pipeline.run({ sink: (message) => Effect.sync(() => collected.push(message)) }),\n Effect.provideService(EmailPipelineCtx, { db: options.db, stats, summaries }),\n );\n\n const threads = buildThreads(collected, { ownerEmail: options.ownerEmail, now: options.now });\n for (const thread of threads) {\n options.db.add(thread);\n }\n return { messages: collected, stats, summaries, threads };\n }),\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { Obj } from '@dxos/echo';\nimport { Message } from '@dxos/types';\n\nimport { messageSource } from '../stages/facts';\nimport { Thread, type ThreadState } from '../types';\nimport { deriveThreadId } from './threading';\n\nconst DEFAULT_STALE_PERIOD_MS = 14 * 24 * 60 * 60 * 1000;\n\nexport type BuildThreadsOptions = {\n readonly ownerEmail: string;\n readonly now: string;\n readonly stalePeriodMs?: number;\n};\n\n// Coarse thread state from who spoke last and how long ago. `resolved` needs a signal we do not yet\n// extract, so it is not inferred in this slice.\nconst computeState = (lastFromOwner: boolean, lastCreated: string, options: BuildThreadsOptions): ThreadState => {\n const idleMs = new Date(options.now).getTime() - new Date(lastCreated).getTime();\n const stale = idleMs > (options.stalePeriodMs ?? DEFAULT_STALE_PERIOD_MS);\n if (stale) {\n return 'stalled';\n }\n return lastFromOwner ? 'awaiting-theirs' : 'awaiting-mine';\n};\n\n// Group messages into canonical Thread objects. Messages are bucketed by derived threadId; within a\n// bucket they are ordered by `created` to find the last speaker.\nexport const buildThreads = (messages: readonly Message.Message[], options: BuildThreadsOptions): Thread[] => {\n const buckets = new Map<string, Message.Message[]>();\n for (const message of messages) {\n const threadId = deriveThreadId(message);\n let bucket = buckets.get(threadId);\n if (!bucket) {\n bucket = [];\n buckets.set(threadId, bucket);\n }\n bucket.push(message);\n }\n\n const threads: Thread[] = [];\n for (const [threadId, bucket] of buckets) {\n const ordered = [...bucket].sort((a, b) => a.created.localeCompare(b.created));\n const last = ordered[ordered.length - 1];\n // Email addresses are case-insensitive, so compare and dedup on a lowercased form; otherwise\n // mixed-case senders split into distinct participants and can misclassify awaiting-mine/theirs.\n const owner = options.ownerEmail.toLowerCase();\n const lastFromOwner = last.sender.email?.toLowerCase() === owner;\n const participants = [...new Set(ordered.flatMap((m) => (m.sender.email ? [m.sender.email.toLowerCase()] : [])))];\n const summaries = ordered.flatMap((m) =>\n typeof m.properties?.summary === 'string' && m.properties.summary.length > 0 ? [m.properties.summary] : [],\n );\n const subject = typeof ordered[0].properties?.subject === 'string' ? ordered[0].properties.subject : threadId;\n\n threads.push(\n Obj.make(Thread, {\n threadId,\n subject,\n summary: summaries.length > 0 ? summaries.join('\\n') : subject,\n state: computeState(lastFromOwner, last.created, options),\n participants,\n messageIds: ordered.map(messageSource),\n openQuestions: [],\n actionItems: [],\n }),\n );\n }\n return threads;\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["Obj", "Schema", "DXN", "Type", "ThreadState", "Literal", "Thread", "makeObject", "make", "Struct", "threadId", "String", "subject", "summary", "state", "participants", "Array", "messageIds", "openQuestions", "actionItems", "Schema", "DXN", "Type", "Topic", "makeObject", "make", "Struct", "label", "String", "summary", "threadIds", "Array", "participants", "keywords", "trim", "DEFAULT_EMAIL_PROMPTS", "topicSummary", "digest", "mergePrompts", "overrides", "DEFAULT_STOPWORDS", "DEFAULT_TOPIC_OPTIONS", "similarityThreshold", "minSharedParticipants", "minTokenLength", "stopwords", "maxKeywords", "tokenize", "subject", "options", "Set", "toLowerCase", "split", "filter", "token", "length", "includes", "jaccard", "left", "right", "size", "shared", "has", "sharedCount", "value", "clusterThreads", "threads", "opts", "clusters", "thread", "signature", "tokens", "participants", "home", "find", "cluster", "members", "push", "add", "participant", "map", "counts", "Map", "member", "set", "get", "keywords", "entries", "sort", "localeCompare", "slice", "summaries", "flatMap", "summary", "first", "label", "join", "threadId", "threadIds", "summarizeTopics", "drafts", "summarize", "prompts", "mergePrompts", "DEFAULT_EMAIL_PROMPTS", "Promise", "all", "draft", "topicSummary", "trim", "materializeTopics", "Obj", "make", "Topic", "Effect", "DEFAULT_COMMITMENT_PREDICATES", "termLabel", "term", "label", "entity", "literal", "toCommitment", "fact", "who", "assertion", "subject", "what", "object", "validTo", "dueBy", "factuality", "confidence", "undefined", "factId", "id", "source", "attribution", "commitmentLedger", "store", "options", "forEach", "predicates", "predicate", "query", "pipe", "map", "results", "seen", "Set", "commitments", "flat", "has", "add", "push", "sort", "a", "b", "localeCompare", "PREFIX", "normalizeSubject", "subject", "value", "test", "replace", "trim", "toLowerCase", "deriveThreadId", "message", "properties", "normalized", "length", "MS_PER_DAY", "buildRollups", "messages", "bySender", "Map", "message", "email", "sender", "toLowerCase", "entry", "get", "created", "threadIds", "set", "push", "threadId", "deriveThreadId", "includes", "rollups", "ordered", "sort", "first", "last", "length", "spanMs", "Date", "getTime", "messageCount", "firstContact", "lastContact", "cadenceDays", "a", "b", "localeCompare", "buildDigest", "input", "options", "generatedAt", "now", "threadCount", "threads", "length", "topicCount", "topics", "stalledThreads", "filter", "thread", "state", "map", "threadId", "awaitingMine", "dueCommitments", "commitments", "commitment", "dueBy", "undefined", "sort", "a", "b", "localeCompare", "topTopics", "threadIds", "label", "slice", "maxTopTopics", "topic", "narrative", "renderDigest", "digest", "join", "who", "what", "narrateDigest", "summarize", "prompts", "mergePrompts", "DEFAULT_EMAIL_PROMPTS", "trim", "Effect", "Stream", "Pipeline", "Context", "emptyStats", "from", "Map", "to", "total", "spam", "EmailPipelineCtx", "Tag", "Effect", "extractContact", "Stage", "extractContactsStage", "Stage", "map", "message", "gen", "db", "EmailPipelineCtx", "result", "extractContact", "source", "object", "created", "add", "Effect", "Stage", "extractFactsStage", "indexFacts", "map", "message", "tryPromise", "pipe", "tapError", "error", "logWarning", "orElse", "succeed", "as", "Effect", "Stage", "Message", "EMAIL_EXTRACTION_RULES", "EMAIL_EXTRACT_OPTIONS", "rules", "messageSource", "message", "messageId", "properties", "length", "sender", "email", "created", "messageToDocument", "text", "extractText", "source", "author", "date", "extractFactsUnitStage", "extract", "Stage", "map", "message", "tryPromise", "pipe", "tapError", "error", "logWarning", "orElse", "succeed", "facts", "foreignId", "messageSource", "key", "keyOf", "Date", "parse", "created", "Effect", "Stage", "statsStage", "Stage", "map", "message", "gen", "stats", "EmailPipelineCtx", "total", "sender", "email", "from", "set", "get", "recipients", "properties", "to", "Array", "isArray", "recipient", "address", "String", "spam", "LanguageModel", "Effect", "Layer", "AiService", "Stage", "Message", "trim", "SUMMARIZE_MODEL", "SUMMARIZE_PROMPT", "trim", "parseSummary", "raw", "match", "summary", "isSpam", "keywords", "parsed", "JSON", "parse", "spam", "is_spam", "Array", "isArray", "map", "keyword", "String", "summarizeStage", "Stage", "message", "gen", "ctx", "EmailPipelineCtx", "text", "Message", "extractText", "generateText", "prompt", "pipe", "provide", "AiService", "model", "orDie", "timeout", "response", "orElse", "succeed", "messageId", "properties", "id", "summaries", "push", "summaryBlock", "_tag", "make", "created", "sender", "blocks", "EmailFactPipeline", "run", "messages", "options", "gen", "stats", "emptyStats", "collected", "fromIterable", "pipe", "statsStage", "extractFactsStage", "indexFacts", "Pipeline", "sink", "message", "sync", "push", "provideService", "EmailPipelineCtx", "db", "summaries", "Effect", "Stream", "Pipeline", "Obj", "DEFAULT_STALE_PERIOD_MS", "computeState", "lastFromOwner", "lastCreated", "options", "idleMs", "Date", "now", "getTime", "stale", "stalePeriodMs", "buildThreads", "messages", "buckets", "Map", "message", "threadId", "deriveThreadId", "bucket", "get", "set", "push", "threads", "ordered", "sort", "a", "b", "created", "localeCompare", "last", "length", "owner", "ownerEmail", "toLowerCase", "sender", "email", "participants", "Set", "flatMap", "m", "summaries", "properties", "summary", "subject", "Obj", "make", "Thread", "join", "state", "messageIds", "map", "messageSource", "openQuestions", "actionItems", "EmailPipeline", "run", "messages", "options", "gen", "stats", "emptyStats", "summaries", "collected", "fromIterable", "pipe", "summarizeStage", "extractContactsStage", "statsStage", "extractFactsStage", "indexFacts", "Pipeline", "sink", "message", "sync", "push", "provideService", "EmailPipelineCtx", "db", "threads", "buildThreads", "ownerEmail", "now", "thread", "add"]
7
+ }
@@ -0,0 +1 @@
1
+ {"inputs":{"src/types/Thread.ts":{"bytes":3393,"imports":[{"path":"effect/Schema","kind":"import-statement","external":true},{"path":"@dxos/echo","kind":"import-statement","external":true}],"format":"esm"},"src/types/Topic.ts":{"bytes":2522,"imports":[{"path":"effect/Schema","kind":"import-statement","external":true},{"path":"@dxos/echo","kind":"import-statement","external":true}],"format":"esm"},"src/types/index.ts":{"bytes":451,"imports":[{"path":"src/types/Thread.ts","kind":"import-statement","original":"./Thread"},{"path":"src/types/Topic.ts","kind":"import-statement","original":"./Topic"}],"format":"esm"},"src/corpus/prompts.ts":{"bytes":3663,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"src/corpus/topics.ts":{"bytes":20788,"imports":[{"path":"@dxos/echo","kind":"import-statement","external":true},{"path":"src/types/index.ts","kind":"import-statement","original":"../types"},{"path":"src/corpus/prompts.ts","kind":"import-statement","original":"./prompts"}],"format":"esm"},"src/corpus/ledger.ts":{"bytes":8360,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true}],"format":"esm"},"src/internal/threading.ts":{"bytes":3565,"imports":[],"format":"esm"},"src/corpus/rollups.ts":{"bytes":7111,"imports":[{"path":"src/internal/threading.ts","kind":"import-statement","original":"../internal/threading"}],"format":"esm"},"src/corpus/digest.ts":{"bytes":11064,"imports":[{"path":"src/corpus/prompts.ts","kind":"import-statement","original":"./prompts"}],"format":"esm"},"src/corpus/index.ts":{"bytes":708,"imports":[{"path":"src/corpus/topics.ts","kind":"import-statement","original":"./topics"},{"path":"src/corpus/ledger.ts","kind":"import-statement","original":"./ledger"},{"path":"src/corpus/rollups.ts","kind":"import-statement","original":"./rollups"},{"path":"src/corpus/digest.ts","kind":"import-statement","original":"./digest"},{"path":"src/corpus/prompts.ts","kind":"import-statement","original":"./prompts"}],"format":"esm"},"src/stages/context.ts":{"bytes":2910,"imports":[{"path":"effect/Context","kind":"import-statement","external":true}],"format":"esm"},"src/stages/extract-contacts.ts":{"bytes":2681,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/extractor-lib","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"src/stages/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"src/stages/extract-facts.ts":{"bytes":3327,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true}],"format":"esm"},"src/stages/facts.ts":{"bytes":5065,"imports":[{"path":"@dxos/types","kind":"import-statement","external":true}],"format":"esm"},"src/stages/extract-facts-commit.ts":{"bytes":5292,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"src/stages/facts.ts","kind":"import-statement","original":"./facts"}],"format":"esm"},"src/stages/stats.ts":{"bytes":3780,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"src/stages/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"src/stages/summarize.ts":{"bytes":11121,"imports":[{"path":"@effect/ai/LanguageModel","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Layer","kind":"import-statement","external":true},{"path":"@dxos/ai","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"@dxos/types","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"src/stages/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"src/stages/index.ts":{"bytes":955,"imports":[{"path":"src/stages/context.ts","kind":"import-statement","original":"./context"},{"path":"src/stages/extract-contacts.ts","kind":"import-statement","original":"./extract-contacts"},{"path":"src/stages/extract-facts.ts","kind":"import-statement","original":"./extract-facts"},{"path":"src/stages/extract-facts-commit.ts","kind":"import-statement","original":"./extract-facts-commit"},{"path":"src/stages/facts.ts","kind":"import-statement","original":"./facts"},{"path":"src/stages/stats.ts","kind":"import-statement","original":"./stats"},{"path":"src/stages/summarize.ts","kind":"import-statement","original":"./summarize"}],"format":"esm"},"src/fact-pipeline.ts":{"bytes":5611,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Stream","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"src/stages/index.ts","kind":"import-statement","original":"./stages"}],"format":"esm"},"src/internal/threads.ts":{"bytes":9887,"imports":[{"path":"@dxos/echo","kind":"import-statement","external":true},{"path":"src/stages/facts.ts","kind":"import-statement","original":"../stages/facts"},{"path":"src/types/index.ts","kind":"import-statement","original":"../types"},{"path":"src/internal/threading.ts","kind":"import-statement","original":"./threading"}],"format":"esm"},"src/pipeline.ts":{"bytes":7432,"imports":[{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Stream","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"src/internal/threads.ts","kind":"import-statement","original":"./internal/threads"},{"path":"src/stages/index.ts","kind":"import-statement","original":"./stages"}],"format":"esm"},"src/index.ts":{"bytes":726,"imports":[{"path":"src/corpus/index.ts","kind":"import-statement","original":"./corpus"},{"path":"src/fact-pipeline.ts","kind":"import-statement","original":"./fact-pipeline"},{"path":"src/pipeline.ts","kind":"import-statement","original":"./pipeline"},{"path":"src/stages/index.ts","kind":"import-statement","original":"./stages"},{"path":"src/types/index.ts","kind":"import-statement","original":"./types"}],"format":"esm"},"src/testing/email-fixtures.ts":{"bytes":7674,"imports":[{"path":"hyparquet","kind":"import-statement","external":true},{"path":"@dxos/types","kind":"import-statement","external":true}],"format":"esm"},"src/testing/index.ts":{"bytes":992,"imports":[{"path":"src/testing/email-fixtures.ts","kind":"import-statement","original":"./email-fixtures"}],"format":"esm"}},"outputs":{"dist/lib/neutral/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":60730},"dist/lib/neutral/index.mjs":{"imports":[{"path":"@dxos/echo","kind":"import-statement","external":true},{"path":"effect/Schema","kind":"import-statement","external":true},{"path":"@dxos/echo","kind":"import-statement","external":true},{"path":"effect/Schema","kind":"import-statement","external":true},{"path":"@dxos/echo","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Stream","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"effect/Context","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/extractor-lib","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"@dxos/types","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"@effect/ai/LanguageModel","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Layer","kind":"import-statement","external":true},{"path":"@dxos/ai","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"@dxos/types","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"effect/Effect","kind":"import-statement","external":true},{"path":"effect/Stream","kind":"import-statement","external":true},{"path":"@dxos/pipeline","kind":"import-statement","external":true},{"path":"@dxos/echo","kind":"import-statement","external":true}],"exports":["DEFAULT_COMMITMENT_PREDICATES","DEFAULT_EMAIL_PROMPTS","DEFAULT_TOPIC_OPTIONS","EMAIL_EXTRACTION_RULES","EMAIL_EXTRACT_OPTIONS","EmailFactPipeline","EmailPipeline","EmailPipelineCtx","Thread","ThreadState","Topic","buildDigest","buildRollups","clusterThreads","commitmentLedger","emptyStats","extractContactsStage","extractFactsStage","extractFactsUnitStage","materializeTopics","mergePrompts","messageSource","messageToDocument","narrateDigest","renderDigest","statsStage","summarizeStage","summarizeTopics"],"entryPoint":"src/index.ts","inputs":{"src/corpus/topics.ts":{"bytesInOutput":3729},"src/types/Thread.ts":{"bytesInOutput":568},"src/types/Topic.ts":{"bytesInOutput":401},"src/corpus/prompts.ts":{"bytesInOutput":603},"src/corpus/ledger.ts":{"bytesInOutput":1111},"src/internal/threading.ts":{"bytesInOutput":466},"src/corpus/rollups.ts":{"bytesInOutput":1246},"src/corpus/digest.ts":{"bytesInOutput":1727},"src/index.ts":{"bytesInOutput":0},"src/fact-pipeline.ts":{"bytesInOutput":1236},"src/stages/context.ts":{"bytesInOutput":260},"src/stages/extract-contacts.ts":{"bytesInOutput":438},"src/stages/extract-facts.ts":{"bytesInOutput":393},"src/stages/extract-facts-commit.ts":{"bytesInOutput":533},"src/stages/facts.ts":{"bytesInOutput":956},"src/stages/stats.ts":{"bytesInOutput":674},"src/stages/summarize.ts":{"bytesInOutput":2260},"src/pipeline.ts":{"bytesInOutput":1396},"src/internal/threads.ts":{"bytesInOutput":1856}},"bytes":21030},"dist/lib/neutral/testing/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":3956},"dist/lib/neutral/testing/index.mjs":{"imports":[{"path":"hyparquet","kind":"import-statement","external":true},{"path":"@dxos/types","kind":"import-statement","external":true}],"exports":["DEFAULT_ENRON_URL","asIso","emailToMessage","loadEnronMessages"],"entryPoint":"src/testing/index.ts","inputs":{"src/testing/email-fixtures.ts":{"bytesInOutput":1131},"src/testing/index.ts":{"bytesInOutput":0}},"bytes":1279}}}
@@ -0,0 +1,48 @@
1
+ // src/testing/email-fixtures.ts
2
+ import { asyncBufferFromUrl, parquetMetadataAsync, parquetReadObjects } from "hyparquet";
3
+ import { Message } from "@dxos/types";
4
+ var DEFAULT_ENRON_URL = "https://huggingface.co/datasets/corbt/enron-emails/resolve/main/data/train-00000-of-00003.parquet";
5
+ var loadEnronMessages = async ({ count = 100, url = DEFAULT_ENRON_URL } = {}) => {
6
+ const file = await asyncBufferFromUrl({
7
+ url
8
+ });
9
+ const metadata = await parquetMetadataAsync(file);
10
+ const rows = await parquetReadObjects({
11
+ file,
12
+ metadata,
13
+ rowStart: 0,
14
+ rowEnd: count
15
+ });
16
+ return rows.map((row) => emailToMessage(row));
17
+ };
18
+ var asIso = (value) => (value instanceof Date ? value : new Date(String(value))).toISOString();
19
+ var emailToMessage = (row) => {
20
+ const block = {
21
+ _tag: "text",
22
+ text: String(row.body ?? "")
23
+ };
24
+ return Message.make({
25
+ created: asIso(row.date),
26
+ sender: {
27
+ email: String(row.from ?? "")
28
+ },
29
+ blocks: [
30
+ block
31
+ ],
32
+ properties: {
33
+ messageId: row.message_id,
34
+ subject: row.subject,
35
+ to: row.to,
36
+ cc: row.cc,
37
+ bcc: row.bcc,
38
+ fileName: row.file_name
39
+ }
40
+ });
41
+ };
42
+ export {
43
+ DEFAULT_ENRON_URL,
44
+ asIso,
45
+ emailToMessage,
46
+ loadEnronMessages
47
+ };
48
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/testing/email-fixtures.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { asyncBufferFromUrl, parquetMetadataAsync, parquetReadObjects } from 'hyparquet';\n\nimport { type ContentBlock, Message } from '@dxos/types';\n\nimport { type ParquetRow } from './parquet';\n\n// Maps rows from the Enron email dataset (see the dataset's `dataset_info` schema) onto ECHO Messages.\n\n/**\n * First shard of the Enron email dataset (https://huggingface.co/datasets/corbt/enron-emails). The\n * HuggingFace CDN serves it with range requests + permissive CORS, so a browser reads only the first\n * N rows (a few row-group byte ranges) rather than the whole ~185 MB file.\n */\nexport const DEFAULT_ENRON_URL =\n 'https://huggingface.co/datasets/corbt/enron-emails/resolve/main/data/train-00000-of-00003.parquet';\n\nexport type LoadEnronOptions = {\n /** Number of messages to read from the start of the dataset. */\n readonly count?: number;\n /** Parquet URL to read from; defaults to {@link DEFAULT_ENRON_URL}. */\n readonly url?: string;\n};\n\n/**\n * Load the first `count` (default 100) Enron messages directly from the dataset parquet over HTTP.\n * Browser-safe: uses hyparquet's URL async buffer (range requests), so only the byte ranges the first\n * rows need are fetched. Rows are mapped to ECHO Messages via {@link emailToMessage}.\n */\nexport const loadEnronMessages = async ({ count = 100, url = DEFAULT_ENRON_URL }: LoadEnronOptions = {}): Promise<\n Message.Message[]\n> => {\n const file = await asyncBufferFromUrl({ url });\n const metadata = await parquetMetadataAsync(file);\n const rows = await parquetReadObjects({ file, metadata, rowStart: 0, rowEnd: count });\n return rows.map((row) => emailToMessage(row as ParquetRow));\n};\n\n/** Coerce a parquet timestamp cell (a `Date`, or a string/number) to an ISO string. */\nexport const asIso = (value: unknown): string =>\n (value instanceof Date ? value : new Date(String(value))).toISOString();\n\n/** Map one email row (see the dataset's `dataset_info` schema) to a Message carrying the body as a text block. */\nexport const emailToMessage = (row: ParquetRow): Message.Message => {\n const block: ContentBlock.Text = { _tag: 'text', text: String(row.body ?? '') };\n return Message.make({\n created: asIso(row.date),\n sender: { email: String(row.from ?? '') },\n blocks: [block],\n properties: {\n messageId: row.message_id,\n subject: row.subject,\n to: row.to,\n cc: row.cc,\n bcc: row.bcc,\n fileName: row.file_name,\n },\n });\n};\n"],
5
+ "mappings": ";AAIA,SAASA,oBAAoBC,sBAAsBC,0BAA0B;AAE7E,SAA4BC,eAAe;AAWpC,IAAMC,oBACX;AAcK,IAAMC,oBAAoB,OAAO,EAAEC,QAAQ,KAAKC,MAAMH,kBAAiB,IAAuB,CAAC,MAAC;AAGrG,QAAMI,OAAO,MAAMR,mBAAmB;IAAEO;EAAI,CAAA;AAC5C,QAAME,WAAW,MAAMR,qBAAqBO,IAAAA;AAC5C,QAAME,OAAO,MAAMR,mBAAmB;IAAEM;IAAMC;IAAUE,UAAU;IAAGC,QAAQN;EAAM,CAAA;AACnF,SAAOI,KAAKG,IAAI,CAACC,QAAQC,eAAeD,GAAAA,CAAAA;AAC1C;AAGO,IAAME,QAAQ,CAACC,WACnBA,iBAAiBC,OAAOD,QAAQ,IAAIC,KAAKC,OAAOF,KAAAA,CAAAA,GAASG,YAAW;AAGhE,IAAML,iBAAiB,CAACD,QAAAA;AAC7B,QAAMO,QAA2B;IAAEC,MAAM;IAAQC,MAAMJ,OAAOL,IAAIU,QAAQ,EAAA;EAAI;AAC9E,SAAOrB,QAAQsB,KAAK;IAClBC,SAASV,MAAMF,IAAIa,IAAI;IACvBC,QAAQ;MAAEC,OAAOV,OAAOL,IAAIgB,QAAQ,EAAA;IAAI;IACxCC,QAAQ;MAACV;;IACTW,YAAY;MACVC,WAAWnB,IAAIoB;MACfC,SAASrB,IAAIqB;MACbC,IAAItB,IAAIsB;MACRC,IAAIvB,IAAIuB;MACRC,KAAKxB,IAAIwB;MACTC,UAAUzB,IAAI0B;IAChB;EACF,CAAA;AACF;",
6
+ "names": ["asyncBufferFromUrl", "parquetMetadataAsync", "parquetReadObjects", "Message", "DEFAULT_ENRON_URL", "loadEnronMessages", "count", "url", "file", "metadata", "rows", "rowStart", "rowEnd", "map", "row", "emailToMessage", "asIso", "value", "Date", "String", "toISOString", "block", "_tag", "text", "body", "make", "created", "date", "sender", "email", "from", "blocks", "properties", "messageId", "message_id", "subject", "to", "cc", "bcc", "fileName", "file_name"]
7
+ }
@@ -0,0 +1,50 @@
1
+ import { type Thread } from '../types';
2
+ import { type Commitment } from './ledger';
3
+ import { type EmailPrompts, type Summarizer } from './prompts';
4
+ import { type RelationshipRollup } from './rollups';
5
+ import { type TopicDraft } from './topics';
6
+ /**
7
+ * Everything the corpus pass has computed; the digest is a Query-mode view over it (spec §3③
8
+ * briefings) — assembled on demand, never materialized as ECHO.
9
+ */
10
+ export type DigestInput = {
11
+ readonly threads: readonly Thread[];
12
+ readonly topics: readonly TopicDraft[];
13
+ readonly commitments: readonly Commitment[];
14
+ readonly rollups: readonly RelationshipRollup[];
15
+ };
16
+ export type DigestOptions = {
17
+ /** Timestamp the digest reports as its generation time (passed in for determinism). */
18
+ readonly now: string;
19
+ /** Topics listed in the skeleton (largest first). */
20
+ readonly maxTopTopics?: number;
21
+ };
22
+ /** Deterministic digest skeleton; `narrative` stays empty until {@link narrateDigest} fills it. */
23
+ export type Digest = {
24
+ readonly generatedAt: string;
25
+ readonly threadCount: number;
26
+ readonly topicCount: number;
27
+ /** Threads needing attention. */
28
+ readonly stalledThreads: readonly string[];
29
+ readonly awaitingMine: readonly string[];
30
+ /** Commitments carrying a deadline, soonest first. */
31
+ readonly dueCommitments: readonly Commitment[];
32
+ readonly topTopics: readonly {
33
+ readonly label: string;
34
+ readonly threadCount: number;
35
+ }[];
36
+ /** LLM briefing text; empty when narration is off or degraded. */
37
+ readonly narrative: string;
38
+ };
39
+ /** Assemble the deterministic digest skeleton from the corpus outputs (Compute mode; no LLM). */
40
+ export declare const buildDigest: (input: DigestInput, options: DigestOptions) => Digest;
41
+ /** Render the skeleton as plain text — the prompt payload for narration, and a fallback briefing. */
42
+ export declare const renderDigest: (digest: Digest) => string;
43
+ /**
44
+ * Optionally add an LLM briefing over the skeleton, using the editable `digest` prompt. Degrades to
45
+ * the un-narrated digest on failure — the skeleton is always available.
46
+ */
47
+ export declare const narrateDigest: (digest: Digest, summarize: Summarizer, options?: {
48
+ readonly prompts?: Partial<EmailPrompts>;
49
+ }) => Promise<Digest>;
50
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../../../src/corpus/digest.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,YAAY,EAAE,KAAK,UAAU,EAAgB,MAAM,WAAW,CAAC;AACpG,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,sDAAsD;IACtD,QAAQ,CAAC,cAAc,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxF,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,WAAW,UAAW,WAAW,WAAW,aAAa,KAAG,MAcvE,CAAC;AAEH,qGAAqG;AACrG,eAAO,MAAM,YAAY,WAAY,MAAM,KAAG,MAYhC,CAAC;AAEf;;;GAGG;AACH,eAAO,MAAM,aAAa,WAChB,MAAM,aACH,UAAU,YACX;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CAAE,KACrD,OAAO,CAAC,MAAM,CAQhB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=digest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.test.d.ts","sourceRoot":"","sources":["../../../../src/corpus/digest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export * from './topics';
2
+ export * from './ledger';
3
+ export * from './rollups';
4
+ export * from './digest';
5
+ export * from './prompts';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/corpus/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
@@ -0,0 +1,33 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { type FactStoreApi, type SemanticIndexError } from '@dxos/pipeline-rdf';
3
+ /**
4
+ * Predicates treated as commitments (spec §3③ commitment ledger). Editable: extraction phrasing
5
+ * varies by corpus and extraction rules, so callers tune this list rather than the query logic.
6
+ */
7
+ export declare const DEFAULT_COMMITMENT_PREDICATES: readonly string[];
8
+ export type LedgerOptions = {
9
+ /** Predicates that mark a fact as a commitment. */
10
+ readonly predicates?: readonly string[];
11
+ };
12
+ /** One row of the commitment ledger, traceable to its supporting fact. */
13
+ export type Commitment = {
14
+ /** Who owes it (fact subject label). */
15
+ readonly who: string;
16
+ /** What is owed (fact object label or literal). */
17
+ readonly what: string;
18
+ /** Deadline (assertion validTo), when stated. */
19
+ readonly dueBy?: string;
20
+ /** Model confidence carried from the fact's factuality. */
21
+ readonly confidence?: number;
22
+ readonly factId: string;
23
+ /** Source message (fact attribution). */
24
+ readonly source: string;
25
+ };
26
+ /**
27
+ * Query the fact store for outstanding commitments — the spec's cross-thread "owe / owed" ledger.
28
+ * Uses the structured query path (one `query({ predicate })` per commitment predicate, compiled to
29
+ * SPARQL on the sqlite backend), which works on both store backends; rows are advisory evidence
30
+ * (§4 canonicality) and each carries its fact id + source message for grounding.
31
+ */
32
+ export declare const commitmentLedger: (store: FactStoreApi, options?: LedgerOptions) => Effect.Effect<Commitment[], SemanticIndexError>;
33
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../../../src/corpus/ledger.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,KAAK,YAAY,EAAY,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE1F;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAAyD,CAAC;AAErH,MAAM,MAAM,aAAa,GAAG;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG;IACvB,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAcF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,UACpB,YAAY,YACT,aAAa,KACtB,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,kBAAkB,CAiB9C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ledger.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.test.d.ts","sourceRoot":"","sources":["../../../../src/corpus/ledger.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * User-editable prompts for the LLM-assisted corpus algorithms. Every slot has a default; callers
3
+ * override any subset via {@link mergePrompts}, so the discovery algorithms stay configurable
4
+ * without forking the pipeline. (Extraction rules are configured separately, via pipeline-rdf
5
+ * `ExtractOptions.rules`.)
6
+ */
7
+ export type EmailPrompts = {
8
+ /** Summarize one topic (cluster of threads); receives the topic label + per-thread summaries. */
9
+ readonly topicSummary: string;
10
+ /** Narrate a corpus digest; receives the deterministic digest skeleton. */
11
+ readonly digest: string;
12
+ };
13
+ export declare const DEFAULT_EMAIL_PROMPTS: EmailPrompts;
14
+ /** Overlay caller overrides onto the defaults; absent slots keep their default. */
15
+ export declare const mergePrompts: (overrides?: Partial<EmailPrompts>) => EmailPrompts;
16
+ /**
17
+ * LLM call boundary used by the corpus algorithms: the harness owns model/runtime plumbing and hands
18
+ * modules a plain async closure (same pattern as `FactIndexer`), so algorithm Effects stay R = never.
19
+ */
20
+ export type Summarizer = (prompt: string) => Promise<string>;
21
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../src/corpus/prompts.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,iGAAiG;IACjG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,YAWnC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,YAAY,eAAgB,OAAO,CAAC,YAAY,CAAC,KAAG,YAG/D,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prompts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.test.d.ts","sourceRoot":"","sources":["../../../../src/corpus/prompts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import { Message } from '@dxos/types';
2
+ /**
3
+ * Per-sender relationship aggregate (spec §3③ relationship rollups). Computed values, not ECHO
4
+ * mutations: `Person` is a closed, canonical schema the pipeline must not write onto, so rollups are
5
+ * consumed by the digest / callers (join back to a Person via the lowercased email and the
6
+ * fact-index reconciliation table); materialization waits for a proper schema home.
7
+ */
8
+ export type RelationshipRollup = {
9
+ /** Lowercased sender address (email addresses are case-insensitive). */
10
+ readonly email: string;
11
+ readonly messageCount: number;
12
+ readonly firstContact: string;
13
+ readonly lastContact: string;
14
+ /** Mean days between messages; absent below two messages. */
15
+ readonly cadenceDays?: number;
16
+ /** Threads this sender participated in (send-side), in first-seen order. */
17
+ readonly threadIds: readonly string[];
18
+ };
19
+ /** Aggregate messages into per-sender rollups, most active sender first. Deterministic (Compute mode). */
20
+ export declare const buildRollups: (messages: readonly Message.Message[]) => RelationshipRollup[];
21
+ //# sourceMappingURL=rollups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollups.d.ts","sourceRoot":"","sources":["../../../../src/corpus/rollups.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAMtC;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,CAAC;AAEF,0GAA0G;AAC1G,eAAO,MAAM,YAAY,aAAc,SAAS,OAAO,CAAC,OAAO,EAAE,KAAG,kBAAkB,EAmCrF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rollups.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollups.test.d.ts","sourceRoot":"","sources":["../../../../src/corpus/rollups.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ import { type Thread, Topic } from '../types';
2
+ import { type EmailPrompts, type Summarizer } from './prompts';
3
+ /**
4
+ * Tunable knobs for the deterministic shared-signal clustering (spec §10.3: agglomeration over
5
+ * participant/keyword overlap; embedding/LLM grouping deferred). All values have defaults so
6
+ * callers can adjust the algorithm without forking it.
7
+ */
8
+ export type TopicOptions = {
9
+ /** Minimum Jaccard similarity between subject-token signatures that joins a topic. */
10
+ readonly similarityThreshold?: number;
11
+ /** Shared-participant count that joins a topic regardless of token overlap. */
12
+ readonly minSharedParticipants?: number;
13
+ /** Subject tokens shorter than this are ignored. */
14
+ readonly minTokenLength?: number;
15
+ /** Tokens excluded from signatures and keywords. */
16
+ readonly stopwords?: readonly string[];
17
+ /** Keywords retained per topic (most frequent first). */
18
+ readonly maxKeywords?: number;
19
+ };
20
+ export declare const DEFAULT_TOPIC_OPTIONS: Required<TopicOptions>;
21
+ /** A topic before ECHO materialization: plain values so LLM enrichment can rewrite them freely. */
22
+ export type TopicDraft = {
23
+ readonly label: string;
24
+ readonly summary: string;
25
+ readonly threadIds: readonly string[];
26
+ readonly participants: readonly string[];
27
+ readonly keywords: readonly string[];
28
+ };
29
+ /**
30
+ * Greedy agglomerative clustering of threads into topic drafts. Deterministic (Compute mode): a
31
+ * thread joins the first cluster whose aggregate signature overlaps enough — by subject-token
32
+ * Jaccard similarity or by shared participants — else starts its own. Bounded by O(threads ×
33
+ * clusters); no LLM involved, so results are stable and unit-testable.
34
+ */
35
+ export declare const clusterThreads: (threads: readonly Thread[], options?: TopicOptions) => TopicDraft[];
36
+ /**
37
+ * Optionally replace each draft's deterministic summary with an LLM one, using the editable
38
+ * `topicSummary` prompt. Degrades per-topic: a failed call keeps that draft's deterministic summary
39
+ * (advisory enrichment must not fail the corpus pass).
40
+ */
41
+ export declare const summarizeTopics: (drafts: readonly TopicDraft[], summarize: Summarizer, options?: {
42
+ readonly prompts?: Partial<EmailPrompts>;
43
+ }) => Promise<TopicDraft[]>;
44
+ /** Materialize drafts as canonical Topic ECHO objects (done last; drafts stay freely mutable). */
45
+ export declare const materializeTopics: (drafts: readonly TopicDraft[]) => Topic[];
46
+ //# sourceMappingURL=topics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topics.d.ts","sourceRoot":"","sources":["../../../../src/corpus/topics.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAyB,KAAK,YAAY,EAAE,KAAK,UAAU,EAAgB,MAAM,WAAW,CAAC;AA4BpG;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,sFAAsF;IACtF,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,+EAA+E;IAC/E,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,oDAAoD;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,YAAY,CAMxD,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAuCF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,YAAa,SAAS,MAAM,EAAE,YAAY,YAAY,KAAG,UAAU,EA4D7F,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,WAClB,SAAS,UAAU,EAAE,aAClB,UAAU,YACX;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CAAE,KACrD,OAAO,CAAC,UAAU,EAAE,CActB,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,WAAY,SAAS,UAAU,EAAE,KAAG,KAAK,EASpE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=topics.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topics.test.d.ts","sourceRoot":"","sources":["../../../../src/corpus/topics.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { type Database } from '@dxos/echo';
3
+ import { Message } from '@dxos/types';
4
+ import { type FactIndexer, type Stats } from './stages';
5
+ export type EmailFactPipelineOptions = {
6
+ /** ECHO space database, required by `EmailPipelineCtx` even though this assembly never writes to it. */
7
+ readonly db: Database.Database;
8
+ /** Persists one message's facts to the fact substrate (a closure over a `FactStore`). */
9
+ readonly indexFacts: FactIndexer;
10
+ };
11
+ export type EmailFactPipelineResult = {
12
+ readonly messages: Message.Message[];
13
+ readonly stats: Stats;
14
+ };
15
+ export declare const EmailFactPipeline: {
16
+ /**
17
+ * Facts-only assembly of the email stages over a batch of messages: stats → extract-facts. Omits
18
+ * summarize, extract-contacts, and {@link buildThreads} from {@link EmailPipeline}, so it needs no
19
+ * `AiService` and never writes to `db`. The cursored `EnrichMailbox` operation (in plugin-inbox)
20
+ * covers the same fact-extraction concern incrementally, via a sibling facts-returning stage
21
+ * (`extractFactsUnitStage`) that commits pages atomically with a cursor advance instead of indexing
22
+ * in-stage; this batch variant is for one-shot runs over an already-collected set of messages.
23
+ */
24
+ run: (messages: readonly Message.Message[], options: EmailFactPipelineOptions) => Effect.Effect<EmailFactPipelineResult>;
25
+ };
26
+ //# sourceMappingURL=fact-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-pipeline.d.ts","sourceRoot":"","sources":["../../../src/fact-pipeline.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAoB,KAAK,WAAW,EAAE,KAAK,KAAK,EAA6C,MAAM,UAAU,CAAC;AAErH,MAAM,MAAM,wBAAwB,GAAG;IACrC,wGAAwG;IACxG,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,iBAAiB;IAC5B;;;;;;;OAOG;IACH,GAAG,aACS,SAAS,OAAO,CAAC,OAAO,EAAE,WAC3B,wBAAwB,KAChC,MAAM,CAAC,MAAM,CAAC,uBAAuB,CAAC;CAa1C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fact-pipeline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-pipeline.test.d.ts","sourceRoot":"","sources":["../../../src/fact-pipeline.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export * from './corpus';
2
+ export * from './fact-pipeline';
3
+ export * from './pipeline';
4
+ export * from './stages';
5
+ export * from './types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { RDF } from '@dxos/pipeline-rdf';
2
+ import { Organization, Person } from '@dxos/types';
3
+ export declare const buildEntityIndex: (objects: readonly (Person.Person | Organization.Organization)[]) => Map<string, string>;
4
+ export declare const reconcileFactEntities: (fact: RDF.Fact, index: Map<string, string>) => {
5
+ subject?: string;
6
+ object?: string;
7
+ };
8
+ //# sourceMappingURL=fact-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-index.d.ts","sourceRoot":"","sources":["../../../../src/internal/fact-index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAqB,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAanD,eAAO,MAAM,gBAAgB,YAClB,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,EAAE,KAC9D,GAAG,CAAC,MAAM,EAAE,MAAM,CAUpB,CAAC;AAIF,eAAO,MAAM,qBAAqB,SAC1B,GAAG,CAAC,IAAI,SACP,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KACzB;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAIrC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fact-index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-index.test.d.ts","sourceRoot":"","sources":["../../../../src/internal/fact-index.test.ts"],"names":[],"mappings":""}