@remnic/core 9.16.2 → 9.17.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 (211) hide show
  1. package/dist/access-admin-ops-surface.d.ts +8 -8
  2. package/dist/access-boundary.d.ts +8 -8
  3. package/dist/access-cli.js +8 -8
  4. package/dist/access-http.d.ts +8 -8
  5. package/dist/access-identity-continuity-surface.d.ts +6 -6
  6. package/dist/access-lcm-surface.d.ts +8 -8
  7. package/dist/access-mcp.d.ts +8 -8
  8. package/dist/access-memory-search-fanout.d.ts +2 -2
  9. package/dist/{access-namespace-preflight-DwhEFYy3.d.ts → access-namespace-preflight-_yxq3PYy.d.ts} +1 -1
  10. package/dist/access-namespace-preflight.d.ts +3 -3
  11. package/dist/access-observe-write-surface.d.ts +8 -8
  12. package/dist/access-operations.d.ts +8 -8
  13. package/dist/access-recall-concurrency.d.ts +8 -8
  14. package/dist/access-recall-response.d.ts +8 -8
  15. package/dist/access-recall-surface.d.ts +8 -8
  16. package/dist/{access-service-CSq1Wp2Z.d.ts → access-service-CSm242ks.d.ts} +6 -6
  17. package/dist/access-service.d.ts +8 -8
  18. package/dist/access-surface-catalog.d.ts +8 -8
  19. package/dist/action-confidence.d.ts +1 -1
  20. package/dist/active-memory-bridge.d.ts +1 -1
  21. package/dist/active-recall.d.ts +1 -1
  22. package/dist/active-recall.js +2 -2
  23. package/dist/{auto-sync-UZG3XQZ5.js → auto-sync-IH3G25MT.js} +2 -2
  24. package/dist/behavior-learner.d.ts +1 -1
  25. package/dist/behavior-signals.d.ts +1 -1
  26. package/dist/bootstrap.d.ts +6 -6
  27. package/dist/briefing.d.ts +2 -2
  28. package/dist/buffer-surprise-report.d.ts +1 -1
  29. package/dist/buffer.d.ts +2 -2
  30. package/dist/bulk-import/index.d.ts +3 -3
  31. package/dist/calibration.d.ts +1 -1
  32. package/dist/capabilities.d.ts +1 -1
  33. package/dist/{catalog-B9uga4sG.d.ts → catalog-9qLG_2OV.d.ts} +1 -1
  34. package/dist/causal-behavior.d.ts +1 -1
  35. package/dist/causal-consolidation.d.ts +1 -1
  36. package/dist/causal-trajectory-graph.d.ts +1 -1
  37. package/dist/{chunk-5EKOBRCA.js → chunk-BCNVNUQG.js} +2 -2
  38. package/dist/{chunk-V6LYAXGW.js → chunk-F7BWFIDK.js} +2 -2
  39. package/dist/{chunk-BTK2PIFL.js → chunk-GBT4TFV7.js} +2 -2
  40. package/dist/{chunk-JOCXI2O5.js → chunk-K2B2TMF3.js} +2 -2
  41. package/dist/{chunk-GSQASASV.js → chunk-MONWCSVL.js} +65 -4
  42. package/dist/chunk-MONWCSVL.js.map +1 -0
  43. package/dist/{chunk-VLIY55Q5.js → chunk-TXXP7WAM.js} +4 -4
  44. package/dist/{chunk-YHURQ3M5.js → chunk-UH7VNQ7O.js} +268 -5
  45. package/dist/chunk-UH7VNQ7O.js.map +1 -0
  46. package/dist/{chunk-5SB7SC2G.js → chunk-UX3ZXN34.js} +3 -1
  47. package/dist/{cli-DYLGXxuu.d.ts → cli-BLZHYY2A.d.ts} +5 -5
  48. package/dist/cli.d.ts +10 -10
  49. package/dist/cli.js +5 -5
  50. package/dist/compounding/engine.d.ts +2 -2
  51. package/dist/compounding/preference-consolidator.d.ts +1 -1
  52. package/dist/compression-optimizer.d.ts +1 -1
  53. package/dist/config.d.ts +1 -1
  54. package/dist/config.js +2 -2
  55. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  56. package/dist/connectors/codex-materialize.d.ts +1 -1
  57. package/dist/connectors/index.d.ts +1 -1
  58. package/dist/consolidation-provenance-check.d.ts +2 -2
  59. package/dist/consolidation-undo.d.ts +2 -2
  60. package/dist/contradiction/index.d.ts +2 -2
  61. package/dist/conversation-index/backend.d.ts +1 -1
  62. package/dist/conversation-index/chunker.d.ts +1 -1
  63. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  64. package/dist/conversation-index/indexer.d.ts +1 -1
  65. package/dist/conversation-index/search.d.ts +1 -1
  66. package/dist/day-summary.d.ts +1 -1
  67. package/dist/delinearize.d.ts +1 -1
  68. package/dist/direct-answer-wiring.d.ts +1 -1
  69. package/dist/direct-answer.d.ts +1 -1
  70. package/dist/embedding-fallback.d.ts +1 -1
  71. package/dist/enrichment/index.d.ts +1 -1
  72. package/dist/entity-retrieval.d.ts +2 -2
  73. package/dist/entity-schema.d.ts +1 -1
  74. package/dist/explicit-capture.d.ts +6 -6
  75. package/dist/extraction-faithfulness.d.ts +1 -1
  76. package/dist/extraction-judge-telemetry.d.ts +1 -1
  77. package/dist/extraction-judge-training.d.ts +1 -1
  78. package/dist/extraction-judge.d.ts +1 -1
  79. package/dist/extraction.d.ts +1 -1
  80. package/dist/fallback-llm.d.ts +1 -1
  81. package/dist/graph-dashboard-diff.d.ts +1 -1
  82. package/dist/graph-dashboard-key.d.ts +1 -1
  83. package/dist/graph-dashboard-parser.d.ts +1 -1
  84. package/dist/graph-edge-reinforcement.d.ts +1 -1
  85. package/dist/graph-snapshot.d.ts +1 -1
  86. package/dist/graph.d.ts +1 -1
  87. package/dist/importance.d.ts +1 -1
  88. package/dist/importers/index.d.ts +1 -1
  89. package/dist/in-flight-reads.d.ts +1 -1
  90. package/dist/index.d.ts +98 -23
  91. package/dist/index.js +14 -8
  92. package/dist/intent.d.ts +1 -1
  93. package/dist/lcm/engine.d.ts +1 -1
  94. package/dist/lcm/index.d.ts +1 -1
  95. package/dist/lcm/tools.d.ts +1 -1
  96. package/dist/lifecycle.d.ts +1 -1
  97. package/dist/live-connectors-runner.d.ts +1 -1
  98. package/dist/local-llm.d.ts +1 -1
  99. package/dist/local-model-endpoint.d.ts +1 -1
  100. package/dist/maintenance/memory-governance.d.ts +1 -1
  101. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  102. package/dist/mcp-memory-inspector-app.d.ts +8 -8
  103. package/dist/memory-action-policy.d.ts +1 -1
  104. package/dist/memory-cache.d.ts +1 -1
  105. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  106. package/dist/memory-projection-store.d.ts +1 -1
  107. package/dist/memory-provenance.d.ts +1 -1
  108. package/dist/memory-worth-outcomes.d.ts +2 -2
  109. package/dist/models-json.d.ts +1 -1
  110. package/dist/namespaces/migrate.d.ts +3 -3
  111. package/dist/namespaces/principal.d.ts +1 -1
  112. package/dist/namespaces/search.d.ts +1 -1
  113. package/dist/namespaces/storage.d.ts +3 -3
  114. package/dist/native-knowledge.d.ts +1 -1
  115. package/dist/offline-sync-impression-drain.d.ts +1 -1
  116. package/dist/operator-toolkit.d.ts +2 -2
  117. package/dist/operator-toolkit.js +3 -3
  118. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  119. package/dist/orchestration/maintenance.d.ts +3 -3
  120. package/dist/orchestration/maintenance.js +2 -2
  121. package/dist/{orchestrator-BHx-DWCa.d.ts → orchestrator-CplS9U3U.d.ts} +5 -5
  122. package/dist/orchestrator.d.ts +6 -6
  123. package/dist/orchestrator.js +8 -8
  124. package/dist/patterns-cli.d.ts +1 -1
  125. package/dist/{pipeline-DDHSE876.d.ts → pipeline-DB4CbFqo.d.ts} +1 -1
  126. package/dist/policy-runtime.d.ts +1 -1
  127. package/dist/proactive-contention.d.ts +1 -1
  128. package/dist/provenance.d.ts +1 -1
  129. package/dist/{qmd-C4WVh6BM.d.ts → qmd-BxZmOLHq.d.ts} +1 -1
  130. package/dist/qmd-preflight.d.ts +2 -2
  131. package/dist/qmd-recall-cache.d.ts +1 -1
  132. package/dist/qmd.d.ts +2 -2
  133. package/dist/recall-concurrency-config.d.ts +1 -1
  134. package/dist/recall-disclosure-escalation.d.ts +1 -1
  135. package/dist/recall-explain-renderer.d.ts +1 -1
  136. package/dist/recall-memory-map.d.ts +1 -1
  137. package/dist/recall-planner-llm.d.ts +1 -1
  138. package/dist/recall-state.d.ts +1 -1
  139. package/dist/recall-tag-filter.d.ts +1 -1
  140. package/dist/recall-timings.d.ts +1 -1
  141. package/dist/recall-xray-cli.d.ts +1 -1
  142. package/dist/recall-xray-renderer.d.ts +1 -1
  143. package/dist/recall-xray.d.ts +1 -1
  144. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  145. package/dist/replay/normalizers/claude.d.ts +1 -1
  146. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  147. package/dist/replay/normalizers/shared.d.ts +1 -1
  148. package/dist/replay/runner.d.ts +1 -1
  149. package/dist/replay/types.d.ts +1 -1
  150. package/dist/resolve-auth-token.d.ts +1 -1
  151. package/dist/resume-bundles.js +3 -3
  152. package/dist/retrieval-agents.d.ts +2 -2
  153. package/dist/retrieval-tiers.d.ts +1 -1
  154. package/dist/routing/engine.d.ts +1 -1
  155. package/dist/routing/store.d.ts +1 -1
  156. package/dist/salvage-envelope.d.ts +1 -1
  157. package/dist/{scope-profiles-EqMXwzCz.d.ts → scope-profiles-NsQQo1Lt.d.ts} +1 -1
  158. package/dist/search/embed-helper.d.ts +1 -1
  159. package/dist/search/factory.d.ts +1 -1
  160. package/dist/search/index.d.ts +1 -1
  161. package/dist/search/lancedb-backend.d.ts +1 -1
  162. package/dist/search/meilisearch-backend.d.ts +1 -1
  163. package/dist/search/noop-backend.d.ts +1 -1
  164. package/dist/search/orama-backend.d.ts +1 -1
  165. package/dist/search/port.d.ts +1 -1
  166. package/dist/search/remote-backend.d.ts +1 -1
  167. package/dist/{semantic-consolidation-CYpvwmQc.d.ts → semantic-consolidation-DW85h30n.d.ts} +1 -1
  168. package/dist/semantic-consolidation.d.ts +2 -2
  169. package/dist/semantic-rule-verifier.d.ts +1 -1
  170. package/dist/session-observer-bands.d.ts +1 -1
  171. package/dist/session-observer-state.d.ts +1 -1
  172. package/dist/shared-context/manager.d.ts +1 -1
  173. package/dist/signal.d.ts +1 -1
  174. package/dist/{storage-Dxd7zVZH.d.ts → storage-o5BTAa8d.d.ts} +1 -1
  175. package/dist/storage.d.ts +2 -2
  176. package/dist/summarizer.d.ts +1 -1
  177. package/dist/summary-snapshot.d.ts +1 -1
  178. package/dist/temporal-supersession.d.ts +2 -2
  179. package/dist/temporal-timeline-recall.d.ts +1 -1
  180. package/dist/temporal-validity.d.ts +1 -1
  181. package/dist/threading.d.ts +1 -1
  182. package/dist/tier-migration.d.ts +2 -2
  183. package/dist/tier-routing.d.ts +1 -1
  184. package/dist/topics.d.ts +1 -1
  185. package/dist/transcript.d.ts +1 -1
  186. package/dist/trust-score-stage.d.ts +1 -1
  187. package/dist/trust-score.d.ts +1 -1
  188. package/dist/{types-OUCJALBK.d.ts → types-V3dT-pen.d.ts} +14 -2
  189. package/dist/types.d.ts +1 -1
  190. package/dist/utility-runtime.d.ts +1 -1
  191. package/dist/write-envelope.d.ts +1 -1
  192. package/package.json +2 -2
  193. package/src/activity/config.test.ts +42 -4
  194. package/src/activity/config.ts +83 -8
  195. package/src/activity/index.ts +5 -3
  196. package/src/activity/memory-gen.test.ts +436 -0
  197. package/src/activity/memory-gen.ts +409 -0
  198. package/src/activity/runner.test.ts +3 -0
  199. package/src/activity/scheduler.test.ts +3 -0
  200. package/src/activity/sync-registration.test.ts +2 -1
  201. package/src/activity/types.ts +15 -1
  202. package/src/config.test.ts +7 -0
  203. package/dist/chunk-GSQASASV.js.map +0 -1
  204. package/dist/chunk-YHURQ3M5.js.map +0 -1
  205. /package/dist/{auto-sync-UZG3XQZ5.js.map → auto-sync-IH3G25MT.js.map} +0 -0
  206. /package/dist/{chunk-5EKOBRCA.js.map → chunk-BCNVNUQG.js.map} +0 -0
  207. /package/dist/{chunk-V6LYAXGW.js.map → chunk-F7BWFIDK.js.map} +0 -0
  208. /package/dist/{chunk-BTK2PIFL.js.map → chunk-GBT4TFV7.js.map} +0 -0
  209. /package/dist/{chunk-JOCXI2O5.js.map → chunk-K2B2TMF3.js.map} +0 -0
  210. /package/dist/{chunk-VLIY55Q5.js.map → chunk-TXXP7WAM.js.map} +0 -0
  211. /package/dist/{chunk-5SB7SC2G.js.map → chunk-UX3ZXN34.js.map} +0 -0
@@ -0,0 +1,409 @@
1
+ import { scoreImportance, isAboveImportanceThreshold } from "../importance.js";
2
+ import { getVerdictKind, type JudgeCandidate, type JudgeVerdict, type JudgeVerdictKind } from "../extraction-judge.js";
3
+ import { log } from "../logger.js";
4
+ import type { ExtractedFact, ExtractionResult, ImportanceScore, MemoryStatus } from "../types.js";
5
+ import {
6
+ composeMemoryEnvelope,
7
+ type MemoryWriteInput,
8
+ type SealedMemoryEnvelope,
9
+ type WriteContext,
10
+ } from "../write-envelope.js";
11
+ import { computeTrustScore, decideSmart, type SmartDecision } from "../wearables/trust.js";
12
+ import { activityDayWindow } from "./digest.js";
13
+ import type { ActivityConfig } from "./types.js";
14
+
15
+ const ACTIVITY_ALLOWED_CATEGORIES: Record<string, true> = {
16
+ decision: true,
17
+ commitment: true,
18
+ preference: true,
19
+ moment: true,
20
+ };
21
+ const FIRST_PERSON = /\b(?:i|we|my|our|i['’]ve|we['’]ve)\b/i;
22
+ /**
23
+ * Attribution detection. On-screen text routinely quotes other people, so a
24
+ * first-person pronoun is not ownership evidence. Two shapes are rejected,
25
+ * case-insensitively, with the speaker slot excluding bare first-person
26
+ * pronouns (word-anchored) so "We decided" stays eligible while names that
27
+ * merely begin with a pronoun ("Wendy", "Ian") do not:
28
+ * - verb attribution: "Alice wrote: I decided ..." (leading speaker + verb)
29
+ * - leading label: "Alice: I decided ..." (chat/comment sender headers)
30
+ * Common document labels ("Note:", "TODO:") are not senders and stay eligible.
31
+ */
32
+ const ATTRIBUTION_SPEAKER = String.raw`(?!(?:i|we|us|me|my|our)\b)[A-Za-z][\w.'’-]*`;
33
+ const SPEECH_VERB =
34
+ String.raw`(?:said|says|wrote|writes|posted|typed|asked|replied|messaged|commented|noted|announced|added|responded|mentioned|told)`;
35
+ const VERB_ATTRIBUTION = new RegExp(String.raw`^\s*${ATTRIBUTION_SPEAKER}\s+${SPEECH_VERB}\b`, "i");
36
+ const LABEL_ATTRIBUTION = new RegExp(
37
+ String.raw`^\s*(${ATTRIBUTION_SPEAKER}(?:\s+[A-Za-z][\w.'’-]*){0,3})\s*:\s*`,
38
+ "i",
39
+ );
40
+ // A sender named inside an otherwise-allowlisted label ("Update from Alice:",
41
+ // "Note by Bob:") is still attribution. Case-sensitive on the capitalized name
42
+ // so generic labels ("Summary by section:") stay eligible.
43
+ const LABEL_NAMES_SENDER = /\b(?:from|by)\s+[A-Z][a-z]/;
44
+ const NON_SENDER_LABELS: Record<string, true> = {
45
+ note: true, notes: true, todo: true, fixme: true, reminder: true, update: true,
46
+ updates: true, warning: true, info: true, tip: true, tips: true, summary: true,
47
+ status: true, tldr: true, fyi: true, idea: true, goal: true, goals: true,
48
+ plan: true, plans: true, action: true, actions: true, context: true, question: true,
49
+ };
50
+ const TRUST_ATTRIBUTE_KEYS: Record<string, true> = {
51
+ trustscore: true,
52
+ trustdecision: true,
53
+ judgeverdict: true,
54
+ };
55
+
56
+ export interface ActivityMemoryWriter {
57
+ /**
58
+ * Locate an existing activity memory for this day whose normalized content
59
+ * matches, or null. The dedup identity is `(activity day, normalized content)`:
60
+ * `dayStartUtc`/`dayEndUtc` bound the half-open day window and `normalizedKey`
61
+ * is trim+lowercase content — so whitespace/case variants collapse, while the
62
+ * SAME text on a different day is NOT a duplicate (recurring actions persist
63
+ * per day). Enables dedup and in-place promotion instead of duplicate or
64
+ * frozen-pending records.
65
+ */
66
+ findActivityMemoryByContent(
67
+ normalizedKey: string,
68
+ dayStartUtc: string,
69
+ dayEndUtc: string,
70
+ ): Promise<{ id: string; status: MemoryStatus | undefined } | null>;
71
+ /**
72
+ * Count active + pending_review activity memories whose event time falls in
73
+ * the half-open [startUtc, endUtc) day window. Seeds `maxMemoriesPerDay` so
74
+ * the cap holds across repeated digest runs on the same day rather than
75
+ * resetting on every call.
76
+ */
77
+ countActivityMemoriesForDay(startUtc: string, endUtc: string): Promise<number>;
78
+ /**
79
+ * Promote a pending_review activity memory to active, merging trust evidence.
80
+ * Returns false when the row is missing or no longer pending.
81
+ */
82
+ promoteActivityMemory(
83
+ id: string,
84
+ attributeUpdates: Record<string, string>,
85
+ confidence?: number,
86
+ ): Promise<boolean>;
87
+ /**
88
+ * Demote a pending_review activity memory to rejected on a fresh judge-reject
89
+ * re-verdict. Active rows are never auto-demoted. Returns false when missing
90
+ * or no longer pending.
91
+ */
92
+ demoteActivityMemory(id: string, attributeUpdates: Record<string, string>): Promise<boolean>;
93
+ writeSealedMemory(
94
+ envelope: SealedMemoryEnvelope,
95
+ extras: { status: MemoryStatus; contentHashSource: string; importance?: ImportanceScore },
96
+ ): Promise<{ tombstoneBlocked?: boolean }>;
97
+ }
98
+
99
+ export interface ActivityMemoryGenerationDeps {
100
+ extract(digestBody: string): Promise<ExtractionResult>;
101
+ judge(candidates: JudgeCandidate[]): Promise<Map<number, JudgeVerdict>>;
102
+ writer: ActivityMemoryWriter;
103
+ now?: () => Date;
104
+ }
105
+
106
+ export interface ActivityMemoryGenerationResult {
107
+ created: number;
108
+ /** Earlier pending_review writes promoted to active by a stronger reassessment. */
109
+ promoted: number;
110
+ /** Earlier pending_review writes retired by a fresh judge-reject verdict. */
111
+ demoted: number;
112
+ pendingReview: number;
113
+ rejectedDisplayedContent: number;
114
+ rejectedByJudge: number;
115
+ skipped: number;
116
+ }
117
+
118
+ /**
119
+ * Activity snapshots contain arbitrary third-party text. Only durable claims
120
+ * explicitly framed as the user's or team's own action, decision, commitment,
121
+ * preference, or moment may enter the trust gate. A first-person pronoun alone
122
+ * is not ownership evidence: quoted or attributed third-party speech (e.g.
123
+ * "Alice wrote: I decided to leave") is rejected.
124
+ */
125
+ export function isEligibleActivityFact(fact: ExtractionResult["facts"][number]): boolean {
126
+ if (ACTIVITY_ALLOWED_CATEGORIES[fact.category] !== true) return false;
127
+ if (!FIRST_PERSON.test(fact.content)) return false;
128
+ if (VERB_ATTRIBUTION.test(fact.content)) return false;
129
+ const label = LABEL_ATTRIBUTION.exec(fact.content);
130
+ if (label !== null) {
131
+ const labelText = label[1].trim();
132
+ const firstToken = labelText.split(/\s+/)[0].toLowerCase();
133
+ if (NON_SENDER_LABELS[firstToken] !== true || LABEL_NAMES_SENDER.test(labelText)) return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ /**
139
+ * Compose an activity envelope in salvage mode (issue #1989 pattern): digest
140
+ * facts are MACHINE-generated, so one malformed optional field must not abort a
141
+ * whole day's writes. Dropped fields are warn-logged, never silent.
142
+ */
143
+ function composeSalvagedActivityEnvelope(
144
+ input: MemoryWriteInput,
145
+ ctx: WriteContext,
146
+ ): SealedMemoryEnvelope {
147
+ const envelope = composeMemoryEnvelope(input, ctx, { salvage: true });
148
+ if (envelope.salvageNotes.length > 0) {
149
+ log.warn(`activity write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
150
+ }
151
+ return envelope;
152
+ }
153
+
154
+ interface WritableCandidate {
155
+ fact: ExtractedFact;
156
+ key: string;
157
+ status: Extract<MemoryStatus, "active" | "pending_review">;
158
+ trust: number;
159
+ trustDecision: string;
160
+ judgeVerdict?: JudgeVerdictKind;
161
+ importance: ImportanceScore;
162
+ }
163
+
164
+ export async function generateActivityMemories(
165
+ date: string,
166
+ digestBody: string,
167
+ config: ActivityConfig,
168
+ deps: ActivityMemoryGenerationDeps,
169
+ ): Promise<ActivityMemoryGenerationResult> {
170
+ const result: ActivityMemoryGenerationResult = {
171
+ created: 0,
172
+ promoted: 0,
173
+ demoted: 0,
174
+ pendingReview: 0,
175
+ rejectedDisplayedContent: 0,
176
+ rejectedByJudge: 0,
177
+ skipped: 0,
178
+ };
179
+ // `extractionMode` is the independent extraction gate (per the config contract);
180
+ // `enabled` gates source ingestion, not extraction of a supplied digest.
181
+ if (config.extractionMode !== "smart" || digestBody.trim().length === 0) return result;
182
+
183
+ // Event-time bound: the digest's local day. Backfilled or delayed digests
184
+ // then land on the day they describe rather than at write time.
185
+ const { startUtc, endUtc } = activityDayWindow(date, config.timezone);
186
+
187
+ let extracted: ExtractionResult;
188
+ try {
189
+ extracted = await deps.extract(digestBody);
190
+ } catch (err) {
191
+ // A provider/parse failure must not throw out of the pass; return the
192
+ // structured zero-result so the caller can retry on the next digest run.
193
+ log.warn(
194
+ `activity extraction failed; skipping the day's memory pass: ${err instanceof Error ? err.message : String(err)}`,
195
+ );
196
+ return result;
197
+ }
198
+ if (extracted.extractionFailure !== undefined) {
199
+ // In-band failure marker (partial/failed provider result): treat like a
200
+ // thrown extraction — warn and return the structured zero-result.
201
+ log.warn(
202
+ `activity extraction reported a failure; skipping the day's memory pass: ${extracted.extractionFailure}`,
203
+ );
204
+ return result;
205
+ }
206
+ const candidates = extracted.facts.filter((fact) => {
207
+ if (!isEligibleActivityFact(fact)) {
208
+ result.rejectedDisplayedContent += 1;
209
+ return false;
210
+ }
211
+ const importance = scoreImportance(fact.content, fact.category, fact.tags);
212
+ if (
213
+ !Number.isFinite(fact.confidence) ||
214
+ fact.confidence < config.minConfidence ||
215
+ !isAboveImportanceThreshold(importance.level, config.minImportance)
216
+ ) {
217
+ result.skipped += 1;
218
+ return false;
219
+ }
220
+ return true;
221
+ });
222
+ let verdicts: Map<number, JudgeVerdict>;
223
+ try {
224
+ verdicts = await deps.judge(candidates.map((fact) => ({
225
+ text: fact.content,
226
+ category: fact.category,
227
+ confidence: fact.confidence,
228
+ tags: fact.tags,
229
+ importanceLevel: scoreImportance(fact.content, fact.category, fact.tags).level,
230
+ })));
231
+ } catch (err) {
232
+ // Degraded smart mode (wearables parity): a judge failure must not abort the
233
+ // day's writes. Fall back to trust scoring alone (no judge accept boost).
234
+ log.warn(
235
+ `activity extraction judge unavailable; using trust scoring only: ${err instanceof Error ? err.message : String(err)}`,
236
+ );
237
+ verdicts = new Map();
238
+ }
239
+
240
+ // Score every candidate once, then keep the STRONGEST (highest-trust) copy per
241
+ // normalized key so a weaker earlier duplicate (e.g. a judge-rejected one)
242
+ // cannot crowd out a stronger later one. The day is fixed for this digest, so
243
+ // the normalized content alone keys the in-run set.
244
+ interface ScoredCandidate {
245
+ fact: ExtractedFact;
246
+ key: string;
247
+ trust: number;
248
+ verdictKind?: JudgeVerdictKind;
249
+ decision: SmartDecision;
250
+ importance: ImportanceScore;
251
+ }
252
+ const strongestByKey = new Map<string, ScoredCandidate>();
253
+ for (let index = 0; index < candidates.length; index += 1) {
254
+ const fact = candidates[index];
255
+ const verdict = verdicts.get(index);
256
+ const verdictKind = verdict === undefined ? undefined : getVerdictKind(verdict);
257
+ const trust = computeTrustScore({
258
+ extractionConfidence: fact.confidence,
259
+ sourceTrust: config.sourceTrust,
260
+ judgeVerdict: verdictKind,
261
+ evidence: { corroboratedBySources: [] },
262
+ });
263
+ const decision = decideSmart(trust, verdictKind, {
264
+ autoApproveTrust: config.autoApproveTrust,
265
+ reviewTrust: config.reviewTrust,
266
+ });
267
+ // Dedup key: trim + lowercase so whitespace/case variants collapse (day-scoped).
268
+ const key = fact.content.trim().toLowerCase();
269
+ const scored: ScoredCandidate = {
270
+ fact,
271
+ key,
272
+ trust,
273
+ verdictKind,
274
+ decision,
275
+ importance: scoreImportance(fact.content, fact.category, fact.tags),
276
+ };
277
+ const prev = strongestByKey.get(key);
278
+ if (prev === undefined) {
279
+ strongestByKey.set(key, scored);
280
+ } else {
281
+ // In-run duplicate: keep the copy with the more durable decision (active >
282
+ // review > drop), breaking ties by trust — so a higher-confidence reject
283
+ // cannot crowd out a lower-trust accepted copy.
284
+ result.skipped += 1;
285
+ const rankOf = (outcome: SmartDecision["outcome"]): number =>
286
+ outcome === "active" ? 2 : outcome === "review" ? 1 : 0;
287
+ const curRank = rankOf(scored.decision.outcome);
288
+ const prevRank = rankOf(prev.decision.outcome);
289
+ if (curRank > prevRank || (curRank === prevRank && trust > prev.trust)) {
290
+ strongestByKey.set(key, scored);
291
+ }
292
+ }
293
+ }
294
+
295
+ const writable: WritableCandidate[] = [];
296
+ for (const scored of strongestByKey.values()) {
297
+ const existing = await deps.writer.findActivityMemoryByContent(scored.key, startUtc, endUtc);
298
+ if (existing !== null) {
299
+ // Same-day duplicate of a persisted row: reassess in place; never write new
300
+ // or use a cap slot. (The same text on another day is not found here.)
301
+ if (existing.status === "pending_review") {
302
+ if (scored.decision.outcome === "drop" && scored.decision.reason === "judge-rejected") {
303
+ if (
304
+ await deps.writer.demoteActivityMemory(existing.id, {
305
+ trustScore: scored.trust.toFixed(3),
306
+ trustDecision: "demoted-by-rejection",
307
+ judgeVerdict: "reject",
308
+ })
309
+ ) {
310
+ result.demoted += 1;
311
+ continue;
312
+ }
313
+ } else if (scored.decision.outcome === "active") {
314
+ if (
315
+ await deps.writer.promoteActivityMemory(
316
+ existing.id,
317
+ {
318
+ trustScore: scored.trust.toFixed(3),
319
+ trustDecision: "promoted-by-reassessment",
320
+ ...(scored.verdictKind !== undefined ? { judgeVerdict: scored.verdictKind } : {}),
321
+ },
322
+ scored.trust,
323
+ )
324
+ ) {
325
+ result.promoted += 1;
326
+ continue;
327
+ }
328
+ }
329
+ }
330
+ result.skipped += 1;
331
+ continue;
332
+ }
333
+ if (scored.decision.outcome === "drop") {
334
+ if (scored.decision.reason === "judge-rejected") result.rejectedByJudge += 1;
335
+ else result.skipped += 1;
336
+ continue;
337
+ }
338
+ writable.push({
339
+ fact: scored.fact,
340
+ key: scored.key,
341
+ status: scored.decision.outcome === "active" ? "active" : "pending_review",
342
+ trust: scored.trust,
343
+ trustDecision: scored.decision.reason,
344
+ ...(scored.verdictKind !== undefined ? { judgeVerdict: scored.verdictKind } : {}),
345
+ importance: scored.importance,
346
+ });
347
+ }
348
+ writable.sort((a, b) => {
349
+ if (a.trust !== b.trust) return b.trust - a.trust;
350
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
351
+ });
352
+
353
+ // Seed the cap from memories already persisted for this day so repeated digest
354
+ // runs cannot each write up to the limit. `0` stays uncapped.
355
+ const alreadyWritten =
356
+ config.maxMemoriesPerDay > 0 ? await deps.writer.countActivityMemoriesForDay(startUtc, endUtc) : 0;
357
+ let remaining =
358
+ config.maxMemoriesPerDay > 0
359
+ ? Math.max(0, config.maxMemoriesPerDay - alreadyWritten)
360
+ : Number.POSITIVE_INFINITY;
361
+
362
+ for (const entry of writable) {
363
+ if (remaining <= 0) {
364
+ result.skipped += 1;
365
+ continue;
366
+ }
367
+ // Trust fields FIRST so the salvage-mode 64-entry cap can never trim these
368
+ // load-bearing keys; extractor attributes ride along after and never override
369
+ // them (collisions are compared against the canonical lowercase form).
370
+ const structuredAttributes: Record<string, string> = {
371
+ trustScore: entry.trust.toFixed(3),
372
+ trustDecision: entry.trustDecision,
373
+ ...(entry.judgeVerdict !== undefined ? { judgeVerdict: entry.judgeVerdict } : {}),
374
+ };
375
+ for (const [key, value] of Object.entries(entry.fact.structuredAttributes ?? {})) {
376
+ if (TRUST_ATTRIBUTE_KEYS[key.trim().toLowerCase()] !== true) structuredAttributes[key] = value;
377
+ }
378
+ const envelope = composeSalvagedActivityEnvelope(
379
+ {
380
+ content: entry.fact.content,
381
+ category: entry.fact.category,
382
+ tags: [...entry.fact.tags, "activity"],
383
+ entityRef: entry.fact.entityRef,
384
+ // Persist the decision-derived trust as confidence; extractor attributes
385
+ // ride along, with trust score/decision + judge verdict added last so
386
+ // downstream sees the active/pending rationale (wearable-path parity).
387
+ confidence: entry.trust,
388
+ validAt: startUtc,
389
+ structuredAttributes,
390
+ sourceConnector: "activity",
391
+ sourceReason: "screen activity digest",
392
+ },
393
+ { source: "activity", now: deps.now },
394
+ );
395
+ const write = await deps.writer.writeSealedMemory(envelope, {
396
+ status: entry.status,
397
+ contentHashSource: entry.key,
398
+ importance: entry.importance,
399
+ });
400
+ if (write.tombstoneBlocked) {
401
+ result.skipped += 1;
402
+ continue;
403
+ }
404
+ if (entry.status === "active") result.created += 1;
405
+ else result.pendingReview += 1;
406
+ remaining -= 1;
407
+ }
408
+ return result;
409
+ }
@@ -8,6 +8,7 @@ import { activityDatabasePath, ActivityStore } from "./store.js";
8
8
  import { activityDigestPath } from "./digest.js";
9
9
  import { runActivitySyncOnce } from "./runner.js";
10
10
  import { activityCursorKey } from "./pipeline.js";
11
+ import { defaultActivityConfig } from "./config.js";
11
12
  import type {
12
13
  ActivityConfig,
13
14
  ActivitySnapshot,
@@ -55,6 +56,7 @@ function fixtureClient(
55
56
 
56
57
  function enabledConfig(overrides: Partial<ActivityConfig> = {}): ActivityConfig {
57
58
  return {
59
+ ...defaultActivityConfig(),
58
60
  enabled: true,
59
61
  timezone: "UTC",
60
62
  syncDays: 1,
@@ -70,6 +72,7 @@ test("disabled config makes no client, no HTTP call, and no store write", async
70
72
  let clientBuilt = false;
71
73
  const summary = await runActivitySyncOnce({
72
74
  config: {
75
+ ...defaultActivityConfig(),
73
76
  enabled: false,
74
77
  timezone: "UTC",
75
78
  syncDays: 1,
@@ -8,6 +8,7 @@ import { ACTIVITY_SYNC_DEFAULT_INTERVAL_MS, ActivitySyncScheduler } from "./sche
8
8
  import { runActivitySyncOnce, type ActivitySyncRunSummary } from "./runner.js";
9
9
  import { ActivityStore } from "./store.js";
10
10
  import { activityCursorKey } from "./pipeline.js";
11
+ import { defaultActivityConfig } from "./config.js";
11
12
  import type { ActivityConfig, ActivitySnapshot, ActivitySourceClient } from "./types.js";
12
13
 
13
14
  /** A hand-driven timer: records armed callbacks + intervals, fires on demand. */
@@ -53,6 +54,7 @@ const SUMMARY: ActivitySyncRunSummary = {
53
54
 
54
55
  function enabledConfig(): ActivityConfig {
55
56
  return {
57
+ ...defaultActivityConfig(),
56
58
  enabled: true,
57
59
  timezone: "UTC",
58
60
  syncDays: 1,
@@ -63,6 +65,7 @@ function enabledConfig(): ActivityConfig {
63
65
 
64
66
  function disabledConfig(): ActivityConfig {
65
67
  return {
68
+ ...defaultActivityConfig(),
66
69
  enabled: false,
67
70
  timezone: "UTC",
68
71
  syncDays: 1,
@@ -8,6 +8,7 @@ import {
8
8
  type ActivitySyncRegistrarDeps,
9
9
  type ActivitySyncSchedulerLike,
10
10
  } from "./sync-registration.js";
11
+ import { defaultActivityConfig } from "./config.js";
11
12
 
12
13
  class FakeScheduler implements ActivitySyncSchedulerLike {
13
14
  starts = 0;
@@ -33,7 +34,7 @@ function makeDeps(overrides: Partial<ActivitySyncRegistrarDeps> = {}): {
33
34
  const created: FakeScheduler[] = [];
34
35
  const state = { retries: 0 };
35
36
  const deps: ActivitySyncRegistrarDeps = {
36
- config: { enabled: true, timezone: "UTC", syncDays: 1, autoSyncIntervalMinutes: 15, sources: [] },
37
+ config: { ...defaultActivityConfig(), enabled: true, sources: [] },
37
38
  memoryDir: "/tmp/remnic-activity-registrar",
38
39
  qmdCollection: "openclaw-engram",
39
40
  secureStoreEnabled: false,
@@ -11,6 +11,10 @@
11
11
  * All timestamps are UTC ISO-8601; day bucketing is half-open [start, end).
12
12
  */
13
13
 
14
+ import type { ImportanceLevel } from "../types.js";
15
+
16
+ export type ActivityExtractionMode = "off" | "smart";
17
+
14
18
  /** One captured on-screen text snapshot (a single window at a single instant). */
15
19
  export interface ActivitySnapshot {
16
20
  /** Store row id (assigned on insert; absent before persistence). */
@@ -92,7 +96,7 @@ export interface ActivitySourceConfig {
92
96
  token?: string;
93
97
  }
94
98
 
95
- /** Opt-in activity synchronization settings. */
99
+ /** Opt-in activity synchronization + trust-gated extraction settings. */
96
100
  export interface ActivityConfig {
97
101
  enabled: boolean;
98
102
  timezone: string;
@@ -100,4 +104,14 @@ export interface ActivityConfig {
100
104
  /** Periodic auto-sync cadence in minutes (default 15). */
101
105
  autoSyncIntervalMinutes: number;
102
106
  sources: ActivitySourceConfig[];
107
+ /** `off` keeps activity searchable only; `smart` trust-gates durable first-person claims. */
108
+ extractionMode: ActivityExtractionMode;
109
+ /** Baseline confidence in activity as a source of the user's own actions. */
110
+ sourceTrust: number;
111
+ autoApproveTrust: number;
112
+ reviewTrust: number;
113
+ minConfidence: number;
114
+ minImportance: ImportanceLevel;
115
+ /** `0` means no count cap. */
116
+ maxMemoriesPerDay: number;
103
117
  }
@@ -2365,5 +2365,12 @@ test("parseConfig forwards activity source settings", () => {
2365
2365
  syncDays: 2,
2366
2366
  autoSyncIntervalMinutes: 15,
2367
2367
  sources: [{ machineLabel: "fixture-machine", baseUrl: "http://127.0.0.1:4319" }],
2368
+ extractionMode: "off",
2369
+ sourceTrust: 0.6,
2370
+ autoApproveTrust: 0.8,
2371
+ reviewTrust: 0.5,
2372
+ minConfidence: 0.7,
2373
+ minImportance: "normal",
2374
+ maxMemoriesPerDay: 0,
2368
2375
  });
2369
2376
  });
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/activity/digest.ts","../src/activity/config.ts"],"sourcesContent":["/**\n * Deterministic screen-activity day-digest renderer (issue #1899).\n *\n * Renders a day's snapshots into markdown with YAML frontmatter, placed at\n * `<memoryDir>/activity/<date>.md` — outside the memory scan roots but inside\n * the QMD collection root (searchable, never auto-recalled). No LLM: the body\n * is a pure, byte-identical function of its inputs so an unchanged day skips\n * rewrite by contentHash. Day bucketing is DST-aware and half-open\n * [start, end) (AGENTS.md §23); sort keys are total with stable tiebreakers\n * (§12).\n */\n\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\n\nimport type { ActivityDayDigest, ActivityDayMeta, ActivitySnapshot } from \"./types.js\";\n\nexport const ACTIVITY_DIGEST_FORMAT_VERSION = 1;\nexport const ACTIVITY_DIR_NAME = \"activity\";\n\n/** Attribute at most this much dwell to a single snapshot (idle gaps capped). */\nconst MAX_DWELL_MS = 15 * 60_000;\n/** Notable-excerpt caps. */\nconst NOTABLE_MAX_WINDOWS = 10;\nconst NOTABLE_EXCERPT_CHARS = 280;\n\nconst DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nexport function isValidActivityDate(date: string): boolean {\n if (typeof date !== \"string\" || !DATE_PATTERN.test(date)) return false;\n // Reject impossible calendar days (e.g. 2026-02-30, 2026-13-01): the UTC\n // round-trip must reproduce the same Y-M-D, else Date normalized an overflow.\n const parsed = new Date(`${date}T00:00:00Z`);\n return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date;\n}\n\nexport function activityDigestPath(memoryDir: string, date: string): string {\n if (!isValidActivityDate(date)) {\n // Never interpolate an unvalidated date into a filesystem path (a `../`\n // would escape <memoryDir>/activity/). Reject loudly.\n throw new RangeError(`Invalid activity date \"${date}\"; expected YYYY-MM-DD.`);\n }\n return path.join(memoryDir, ACTIVITY_DIR_NAME, `${date}.md`);\n}\n\n// ── DST-aware local-day window ──────────────────────────────────────────────\n\nfunction timezoneOffsetIso(instant: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n timeZoneName: \"longOffset\",\n }).formatToParts(instant);\n const name = parts.find((part) => part.type === \"timeZoneName\")?.value ?? \"GMT\";\n const match = name.match(/GMT([+-]\\d{2}:\\d{2})?/);\n return match?.[1] ?? \"+00:00\";\n}\n\nexport function assertValidTimezone(timezone: string): void {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: timezone });\n } catch {\n throw new RangeError(`Invalid IANA timezone \"${timezone}\".`);\n }\n}\n\nfunction zonedDayStartIso(date: string, timezone: string): string {\n // The first UTC instant whose local wall-clock is this date at 00:00. Probe\n // several instants across the day to collect every offset in play; keep an\n // offset only if constructing local midnight with it lands back on that same\n // offset (so it is genuinely local midnight, not a wall-clock that never\n // occurred), then take the EARLIEST such instant — the FIRST 00:00 across a\n // DST fall-back that repeats local midnight.\n // Probe the previous UTC day too: for zones east of UTC the requested date's\n // UTC instants all fall after local midnight, so the pre-transition offset\n // valid at the real local 00:00 is only observable on the prior UTC day.\n const prevDate = shiftIsoDate(date, -1);\n const probeOffsets = new Set(\n [\n `${prevDate}T12:00:00Z`,\n `${prevDate}T23:00:00Z`,\n `${date}T00:00:00Z`,\n `${date}T12:00:00Z`,\n `${date}T23:00:00Z`,\n ].map((iso) => timezoneOffsetIso(new Date(iso), timezone)),\n );\n let best: number | null = null;\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T00:00:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n // Reject an offset whose local midnight does not actually occur (the wall\n // clock skipped by a spring-forward): the offset in effect at the candidate\n // instant must be the same offset we used to build it.\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n if (best === null) {\n // Local midnight was skipped by a spring-forward at 00:00. Advance to the\n // first local wall-clock minute on this date that actually exists (never\n // backdating to a 00:00 that never occurred), scanning forward up to 3h.\n for (let minute = 1; minute <= 180 && best === null; minute++) {\n const hh = String(Math.floor(minute / 60)).padStart(2, \"0\");\n const mm = String(minute % 60).padStart(2, \"0\");\n for (const offset of probeOffsets) {\n const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);\n if (!Number.isFinite(candidate)) continue;\n if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;\n if (best === null || candidate < best) best = candidate;\n }\n }\n }\n if (best === null) {\n // Degenerate safety net: use the noon-derived offset for a deterministic start.\n const noon = timezoneOffsetIso(new Date(`${date}T12:00:00Z`), timezone);\n best = Date.parse(`${date}T00:00:00${noon}`);\n }\n if (best === null || !Number.isFinite(best)) {\n throw new RangeError(`activity: could not resolve a local day start for \"${date}\" in \"${timezone}\".`);\n }\n return new Date(best).toISOString();\n}\n\nfunction shiftIsoDate(date: string, days: number): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() + days);\n return parsed.toISOString().slice(0, 10);\n}\n\nfunction nextIsoDate(date: string): string {\n return shiftIsoDate(date, 1);\n}\n\n/** Half-open [start, end) UTC ISO bounds of a local day. */\nexport function activityDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {\n if (!isValidActivityDate(date)) {\n throw new RangeError(`Invalid activity date \"${date}\"; expected a real YYYY-MM-DD day.`);\n }\n assertValidTimezone(timezone);\n return {\n startUtc: new Date(zonedDayStartIso(date, timezone)).toISOString(),\n endUtc: new Date(zonedDayStartIso(nextIsoDate(date), timezone)).toISOString(),\n };\n}\n\n// ── Rendering ───────────────────────────────────────────────────────────────\n\nfunction sortedByTime(snapshots: ActivitySnapshot[]): ActivitySnapshot[] {\n return [...snapshots].sort((a, b) => {\n // Compare by parsed instant, not raw string, so mixed valid ISO forms\n // (Z vs offset, varying precision) still order chronologically.\n const at = Date.parse(a.capturedAtUtc);\n const bt = Date.parse(b.capturedAtUtc);\n if (at !== bt && Number.isFinite(at) && Number.isFinite(bt)) return at < bt ? -1 : 1;\n const aid = a.id ?? 0;\n const bid = b.id ?? 0;\n if (aid < bid) return -1;\n if (aid > bid) return 1;\n // Unsaved snapshots (pre-store) both default to id 0; fall back to the\n // content hash (their dedup identity), then app/window, for a total order.\n if (a.contentHash !== b.contentHash) return a.contentHash < b.contentHash ? -1 : 1;\n if (a.app !== b.app) return a.app < b.app ? -1 : 1;\n if (a.windowTitle !== b.windowTitle) return a.windowTitle < b.windowTitle ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * Per-snapshot dwell (gap to that machine's next snapshot, capped), scoped per\n * capture machine so an interleaved snapshot from another machine can't steal\n * or truncate a snapshot's dwell (multi-machine days).\n */\nfunction computeDwell(snapshots: ActivitySnapshot[]): Map<ActivitySnapshot, number> {\n const byMachine = new Map<string, ActivitySnapshot[]>();\n for (const snapshot of snapshots) {\n const list = byMachine.get(snapshot.machine);\n if (list === undefined) byMachine.set(snapshot.machine, [snapshot]);\n else list.push(snapshot);\n }\n const dwell = new Map<ActivitySnapshot, number>();\n for (const list of byMachine.values()) {\n const ordered = sortedByTime(list);\n for (let index = 0; index < ordered.length; index++) {\n const current = ordered[index];\n if (current === undefined) continue;\n const next = ordered[index + 1];\n let value = 0;\n if (next !== undefined) {\n const delta = Date.parse(next.capturedAtUtc) - Date.parse(current.capturedAtUtc);\n if (Number.isFinite(delta) && delta > 0) value = Math.min(delta, MAX_DWELL_MS);\n }\n dwell.set(current, value);\n }\n }\n return dwell;\n}\n\nfunction formatDurationMinutes(ms: number): string {\n return `${Math.round(ms / 60_000)}m`;\n}\n\nfunction clockHhMm(iso: string, timezone: string): string {\n const ms = Date.parse(iso);\n if (!Number.isFinite(ms)) return \"??:??\";\n const parts = new Intl.DateTimeFormat(\"en-GB\", {\n timeZone: timezone,\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n }).formatToParts(new Date(ms));\n const hour = parts.find((p) => p.type === \"hour\")?.value ?? \"00\";\n const minute = parts.find((p) => p.type === \"minute\")?.value ?? \"00\";\n return `${hour}:${minute}`;\n}\n\nfunction collapseWhitespace(text: string): string {\n return text.replace(/\\s+/g, \" \").trim();\n}\n\nfunction perAppSection(ordered: ActivitySnapshot[], dwell: Map<ActivitySnapshot, number>): string {\n const totals = new Map<string, number>();\n for (const snapshot of ordered) {\n totals.set(snapshot.app, (totals.get(snapshot.app) ?? 0) + (dwell.get(snapshot) ?? 0));\n }\n const rows = [...totals.entries()].sort((a, b) => {\n if (b[1] !== a[1]) return b[1] - a[1];\n if (a[0] < b[0]) return -1;\n if (a[0] > b[0]) return 1;\n return 0;\n });\n const lines = [\"## Per-app time\", \"\"];\n if (rows.length === 0) {\n lines.push(\"_No activity recorded._\");\n } else {\n for (const [app, ms] of rows) {\n lines.push(`- ${app}: ${formatDurationMinutes(ms)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\ninterface TimelineSpan {\n startIso: string;\n machine: string;\n app: string;\n windowTitle: string;\n browserUrl?: string;\n}\n\nfunction timelineSpans(ordered: ActivitySnapshot[]): TimelineSpan[] {\n const spans: TimelineSpan[] = [];\n for (const snapshot of ordered) {\n const last = spans[spans.length - 1];\n if (\n last !== undefined &&\n last.machine === snapshot.machine &&\n last.app === snapshot.app &&\n last.windowTitle === snapshot.windowTitle &&\n last.browserUrl === snapshot.browserUrl\n ) {\n continue;\n }\n spans.push({\n startIso: snapshot.capturedAtUtc,\n machine: snapshot.machine,\n app: snapshot.app,\n windowTitle: snapshot.windowTitle,\n ...(snapshot.browserUrl !== undefined ? { browserUrl: snapshot.browserUrl } : {}),\n });\n }\n return spans;\n}\n\nfunction timelineSection(ordered: ActivitySnapshot[], timezone: string): string {\n const lines = [\"## Timeline\", \"\"];\n const spans = timelineSpans(ordered);\n if (spans.length === 0) {\n lines.push(\"_No activity recorded._\");\n return lines.join(\"\\n\");\n }\n for (const span of spans) {\n const clock = clockHhMm(span.startIso, timezone);\n const window = collapseWhitespace(span.windowTitle);\n const url = span.browserUrl !== undefined ? ` (${collapseWhitespace(span.browserUrl)})` : \"\";\n lines.push(`- [${clock}] ${span.app}${window.length > 0 ? ` — ${window}` : \"\"}${url}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction notableSection(ordered: ActivitySnapshot[], dwell: Map<ActivitySnapshot, number>): string {\n const withDwell = ordered.map((snapshot) => ({ snapshot, dwell: dwell.get(snapshot) ?? 0 }));\n const ranked = withDwell\n .filter((entry) => collapseWhitespace(entry.snapshot.text).length > 0)\n .sort((a, b) => {\n if (b.dwell !== a.dwell) return b.dwell - a.dwell;\n const at = Date.parse(a.snapshot.capturedAtUtc);\n const bt = Date.parse(b.snapshot.capturedAtUtc);\n if (at !== bt && Number.isFinite(at) && Number.isFinite(bt)) return at < bt ? -1 : 1;\n const idDelta = (a.snapshot.id ?? 0) - (b.snapshot.id ?? 0);\n if (idDelta !== 0) return idDelta;\n if (a.snapshot.contentHash !== b.snapshot.contentHash) {\n return a.snapshot.contentHash < b.snapshot.contentHash ? -1 : 1;\n }\n return 0;\n })\n .slice(0, NOTABLE_MAX_WINDOWS);\n const lines = [\"## Notable\", \"\"];\n if (ranked.length === 0) {\n lines.push(\"_No notable text captured._\");\n return lines.join(\"\\n\");\n }\n for (const { snapshot } of ranked) {\n const excerpt = collapseWhitespace(snapshot.text).slice(0, NOTABLE_EXCERPT_CHARS);\n lines.push(`- **${snapshot.app}** — ${excerpt}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function composeActivityDigestBody(\n date: string,\n timezone: string,\n snapshots: ActivitySnapshot[],\n): string {\n assertValidTimezone(timezone);\n const ordered = sortedByTime(snapshots);\n const dwell = computeDwell(ordered);\n return [\n `# Activity — ${date}`,\n \"\",\n perAppSection(ordered, dwell),\n \"\",\n timelineSection(ordered, timezone),\n \"\",\n notableSection(ordered, dwell),\n \"\",\n ].join(\"\\n\");\n}\n\nexport function hashActivityBody(body: string): string {\n return createHash(\"sha256\").update(body, \"utf8\").digest(\"hex\");\n}\n\nexport function composeActivityDigestMeta(\n date: string,\n machines: string[],\n snapshots: ActivitySnapshot[],\n body: string,\n): ActivityDayMeta {\n const uniqueMachines = [...new Set(machines)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n return {\n kind: \"activity-digest\",\n date,\n machines: uniqueMachines,\n snapshotCount: snapshots.length,\n contentHash: hashActivityBody(body),\n formatVersion: ACTIVITY_DIGEST_FORMAT_VERSION,\n };\n}\n\nexport function serializeActivityDigest(meta: ActivityDayMeta, body: string): string {\n const frontmatter = [\n \"---\",\n `kind: ${meta.kind}`,\n `date: ${meta.date}`,\n `machines: ${JSON.stringify(meta.machines)}`,\n `snapshotCount: ${meta.snapshotCount}`,\n `contentHash: ${meta.contentHash}`,\n `formatVersion: ${meta.formatVersion}`,\n \"---\",\n \"\",\n ].join(\"\\n\");\n return `${frontmatter}${body}`;\n}\n\nexport function parseActivityDigest(raw: string): ActivityDayDigest | null {\n if (typeof raw !== \"string\" || !raw.startsWith(\"---\\n\")) return null;\n const end = raw.indexOf(\"\\n---\\n\", 4);\n if (end === -1) return null;\n const frontmatter = raw.slice(4, end);\n const body = raw.slice(end + 5).replace(/^\\n/, \"\");\n const fields = new Map<string, string>();\n for (const line of frontmatter.split(\"\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx === -1) continue;\n fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());\n }\n const date = fields.get(\"date\");\n const contentHash = fields.get(\"contentHash\");\n if (date === undefined || !isValidActivityDate(date) || contentHash === undefined) return null;\n if (fields.get(\"kind\") !== \"activity-digest\") return null;\n const machines = parseMachinesList(fields.get(\"machines\") ?? \"[]\");\n const snapshotCount = parseNonNegativeInt(fields.get(\"snapshotCount\"));\n const formatVersion = parseNonNegativeInt(fields.get(\"formatVersion\"));\n if (snapshotCount === null || formatVersion === null) return null;\n return {\n meta: {\n kind: \"activity-digest\",\n date,\n machines,\n snapshotCount,\n contentHash,\n formatVersion,\n },\n body,\n };\n}\n\nfunction parseNonNegativeInt(value: string | undefined): number | null {\n if (value === undefined) return null;\n const trimmed = value.trim();\n if (!/^\\d+$/.test(trimmed)) return null;\n const parsed = Number(trimmed);\n return Number.isSafeInteger(parsed) ? parsed : null;\n}\n\nfunction parseMachinesList(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.filter((entry): entry is string => typeof entry === \"string\");\n }\n } catch {\n // Legacy unquoted inline form (`[a, b]`) — fall through to the split parse.\n }\n return raw\n .replace(/^\\[/, \"\")\n .replace(/\\]$/, \"\")\n .split(\",\")\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n}\n","import { coerceBooleanLike, coerceNumber } from \"../connectors/coerce.js\";\nimport { assertValidTimezone } from \"./digest.js\";\nimport type { ActivityConfig, ActivitySourceConfig } from \"./types.js\";\n\nfunction asRecord(value: unknown, name: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new TypeError(`${name} must be an object`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction optionalString(value: unknown, name: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value.length === 0) throw new TypeError(`${name} must be a non-empty string`);\n return value;\n}\n\nconst LOOPBACK_HOSTS: Record<string, true> = { localhost: true, \"127.0.0.1\": true, \"::1\": true };\n\n/**\n * Parse, protocol-check, and confine an activity source base URL to a local\n * loopback host. Shared with ActivityHttpSourceClient so config-load and client\n * construction reject the exact same shapes with the same prefixed message (a\n * bare `new URL()` throws an opaque TypeError on malformed input). The bearer\n * token travels in an Authorization header, so a non-loopback baseUrl would\n * exfiltrate it; the subsystem contract is local capture daemons only.\n */\nexport function validateActivityBaseUrl(baseUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new RangeError(`activity source baseUrl must be a valid URL: ${baseUrl}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new RangeError(\"activity source baseUrl must use HTTP or HTTPS\");\n }\n // URL keeps IPv6 hosts bracketed (e.g. \"[::1]\"); normalize before the lookup.\n const host = parsed.hostname.replace(/^\\[|\\]$/g, \"\");\n if (LOOPBACK_HOSTS[host] !== true && !/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host)) {\n throw new RangeError(`activity source baseUrl must target a loopback host (got ${parsed.hostname})`);\n }\n return parsed;\n}\n\nfunction parseSource(value: unknown): ActivitySourceConfig {\n const raw = asRecord(value, \"activity source\");\n const machineLabel = optionalString(raw.machineLabel, \"activity source machineLabel\");\n const baseUrl = optionalString(raw.baseUrl, \"activity source baseUrl\");\n if (machineLabel === undefined || baseUrl === undefined) {\n throw new TypeError(\"activity source requires machineLabel and baseUrl\");\n }\n // Reject whitespace-only labels here so config-load fails loudly rather than\n // parsing clean and then failing on the first sync (the client and pipeline\n // both reject a blank label via trim()).\n if (machineLabel.trim().length === 0) {\n throw new RangeError(\"activity source machineLabel must not be blank\");\n }\n validateActivityBaseUrl(baseUrl);\n const token = optionalString(raw.token, \"activity source token\");\n return { machineLabel, baseUrl, ...(token === undefined ? {} : { token }) };\n}\n\nexport function parseActivityConfig(raw: unknown): ActivityConfig {\n if (raw === undefined || raw === null) {\n return { enabled: false, timezone: \"UTC\", syncDays: 1, autoSyncIntervalMinutes: 15, sources: [] };\n }\n const config = asRecord(raw, \"activity\");\n const enabledValue = coerceBooleanLike(config.enabled, \"activity.enabled\");\n if (config.enabled !== undefined && enabledValue === undefined) {\n throw new TypeError(\"activity.enabled must be a boolean\");\n }\n const timezone = optionalString(config.timezone, \"activity.timezone\") ?? \"UTC\";\n // Validate the IANA zone at parse so a typo fails before any daemon is\n // contacted; otherwise activityDayWindow() throws mid-sync after snapshot\n // pages may already be persisted, and every retry replays the same window.\n assertValidTimezone(timezone);\n const syncDaysValue = coerceNumber(config.syncDays, \"activity.syncDays\");\n if (config.syncDays !== undefined && syncDaysValue === undefined) {\n throw new TypeError(\"activity.syncDays must be a finite number\");\n }\n const syncDays = syncDaysValue ?? 1;\n if (!Number.isInteger(syncDays) || syncDays < 1 || syncDays > 90) {\n throw new RangeError(\"activity.syncDays must be an integer from 1 to 90\");\n }\n const intervalValue = coerceNumber(config.autoSyncIntervalMinutes, \"activity.autoSyncIntervalMinutes\");\n if (config.autoSyncIntervalMinutes !== undefined && intervalValue === undefined) {\n throw new TypeError(\"activity.autoSyncIntervalMinutes must be a finite number\");\n }\n const autoSyncIntervalMinutes = intervalValue ?? 15;\n if (!Number.isInteger(autoSyncIntervalMinutes) || autoSyncIntervalMinutes < 1 || autoSyncIntervalMinutes > 1440) {\n throw new RangeError(\"activity.autoSyncIntervalMinutes must be an integer from 1 to 1440\");\n }\n if (config.sources !== undefined && !Array.isArray(config.sources)) {\n throw new TypeError(\"activity.sources must be an array\");\n }\n const sources = (config.sources ?? []).map(parseSource);\n // Cursors and row dedup are keyed on machineLabel alone; two sources sharing\n // a label would share a cursor and dedup namespace, silently skipping or\n // conflating snapshots. Reject the collision at parse.\n const labels = new Set<string>();\n for (const source of sources) {\n if (labels.has(source.machineLabel)) {\n throw new RangeError(`activity source machineLabel must be unique: ${source.machineLabel}`);\n }\n labels.add(source.machineLabel);\n }\n const enabled = enabledValue ?? false;\n if (enabled && sources.length === 0) throw new RangeError(\"activity.enabled requires at least one source\");\n return { enabled, timezone, syncDays, autoSyncIntervalMinutes, sources };\n}\n"],"mappings":"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","names":[]}