@remnic/core 9.10.0 → 9.12.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.
package/dist/index.d.ts CHANGED
@@ -87,6 +87,7 @@ export { EnrichmentAuditEntry, EnrichmentCandidate, EnrichmentCostTier, Enrichme
87
87
  export { clearBulkImportSources, getBulkImportSource, listBulkImportSources, registerBulkImportSource } from './bulk-import/index.js';
88
88
  export { a as ProcessBatchContext, P as ProcessBatchFn, b as ProcessBatchResult, f as formatBatchTranscript, r as resolveBulkImportContext, c as runBulkImportPipeline, v as validateBatchSize } from './pipeline-Cn-kvYeV.js';
89
89
  export { B as BulkImportCliCommandOptions, p as parseStrictCliDate, r as runBulkImportCliCommand } from './cli-5tNLVTvJ.js';
90
+ import { BetterSqlite3Database } from './runtime/better-sqlite.js';
90
91
  export { DEFAULT_IMPORT_BATCH_SIZE, ImportProgress, ImportedMemory, ImporterAdapter, ImporterParseOptions, ImporterTransformOptions, ImporterWriteResult, ImporterWriteTarget, RunImportOptions, RunImporterResult, defaultWriteMemoriesToOrchestrator, importedMemoryToTurn, runImporter, validateImportBatchSize, validateImportRateLimit } from './importers/index.js';
91
92
  export { FallbackLlmClient, FallbackLlmOptions, FallbackLlmResponse, FallbackLlmRuntimeContext } from './fallback-llm.js';
92
93
  export { ComputeMemoryWorthInput, MemoryWorthResult, computeMemoryWorth } from './memory-worth.js';
@@ -121,7 +122,6 @@ import './namespaces/search.js';
121
122
  import './consolidation-operator.js';
122
123
  import './memory-projection-store.js';
123
124
  import './maintenance/memory-governance.js';
124
- import './runtime/better-sqlite.js';
125
125
  import 'better-sqlite3';
126
126
  import './compounding/engine.js';
127
127
  import './shared-context/manager.js';
@@ -6818,6 +6818,158 @@ interface WearablesCliIo {
6818
6818
  */
6819
6819
  declare function runWearablesCliCommand(service: WearablesService, args: string[], io: WearablesCliIo): Promise<number>;
6820
6820
 
6821
+ /**
6822
+ * Screen-activity subsystem — shared types (issue #1899).
6823
+ *
6824
+ * A third ingestion modality alongside wearables (conversations) and live
6825
+ * connectors (documents). Screen text has no speakers and is high-volume, so it
6826
+ * gets its own store and day-digest rather than being forced into the wearable
6827
+ * conversation shape. Capture daemons live in the à-la-carte `@remnic/capture-screen`
6828
+ * package; this core subsystem is host-agnostic and consumes snapshots over a
6829
+ * loopback HTTP client.
6830
+ *
6831
+ * All timestamps are UTC ISO-8601; day bucketing is half-open [start, end).
6832
+ */
6833
+ /** One captured on-screen text snapshot (a single window at a single instant). */
6834
+ interface ActivitySnapshot {
6835
+ /** Store row id (assigned on insert; absent before persistence). */
6836
+ id?: number;
6837
+ /** Capture-machine label (disambiguates multi-machine stores). */
6838
+ machine: string;
6839
+ /** UTC ISO-8601 capture instant. */
6840
+ capturedAtUtc: string;
6841
+ /** Frontmost application name. */
6842
+ app: string;
6843
+ /** Frontmost window title. */
6844
+ windowTitle: string;
6845
+ /** Browser tab URL, when the frontmost window is a known browser. */
6846
+ browserUrl?: string;
6847
+ /** Extracted visible text (accessibility tree or OCR). */
6848
+ text: string;
6849
+ /** Where the text came from. */
6850
+ textSource: "ax" | "ocr";
6851
+ /** SHA-256 of the normalized snapshot content (idempotency key). */
6852
+ contentHash: string;
6853
+ /** 64-bit SimHash (hex) for near-duplicate detection, when computed. */
6854
+ simhash?: string;
6855
+ }
6856
+ /** Frontmatter persisted on a rendered day digest. */
6857
+ interface ActivityDayMeta {
6858
+ kind: "activity-digest";
6859
+ /** Local day, YYYY-MM-DD. */
6860
+ date: string;
6861
+ /** Machines that contributed snapshots, sorted. */
6862
+ machines: string[];
6863
+ snapshotCount: number;
6864
+ /** SHA-256 of the rendered body (rebuild-idempotency). */
6865
+ contentHash: string;
6866
+ formatVersion: number;
6867
+ }
6868
+ /** A parsed day digest (frontmatter + rendered body). */
6869
+ interface ActivityDayDigest {
6870
+ meta: ActivityDayMeta;
6871
+ body: string;
6872
+ }
6873
+ /** Result of a capture-daemon auth/health probe. */
6874
+ interface ActivitySourceCheck {
6875
+ ok: boolean;
6876
+ detail?: string;
6877
+ }
6878
+ /** One page of snapshots pulled from a capture daemon. */
6879
+ interface ActivitySnapshotPage {
6880
+ snapshots: ActivitySnapshot[];
6881
+ nextCursor: string | null;
6882
+ }
6883
+ /**
6884
+ * Client contract for a screen-capture daemon (one per capture machine).
6885
+ * Implemented by a later slice (the HTTP source client); defined here so the
6886
+ * store and pipeline can be built and tested against a fixture double first.
6887
+ */
6888
+ interface ActivitySourceClient {
6889
+ /** Stable capture-machine label. */
6890
+ machineLabel: string;
6891
+ /** Probe connectivity/auth without mutating anything. */
6892
+ verify(signal?: AbortSignal): Promise<ActivitySourceCheck>;
6893
+ /** Fetch one page of snapshots for a single local day. */
6894
+ fetchSnapshots(opts: {
6895
+ date: string;
6896
+ timezone: string;
6897
+ cursor?: string | null;
6898
+ signal?: AbortSignal;
6899
+ }): Promise<ActivitySnapshotPage>;
6900
+ }
6901
+
6902
+ /**
6903
+ * Screen-activity SQLite store (issue #1899).
6904
+ *
6905
+ * Durable, capture-machine-agnostic store for on-screen text snapshots plus a
6906
+ * per-machine sync cursor. Mirrors the LCM store conventions
6907
+ * (packages/remnic-core/src/lcm/schema.ts): WAL, a `<name>_meta` schema-version
6908
+ * row, `CREATE TABLE IF NOT EXISTS`, and an FTS5 virtual table created
6909
+ * separately. better-sqlite3 is synchronous.
6910
+ *
6911
+ * Snapshots dedup on `(machine, content_hash)` so a re-sync is idempotent, and
6912
+ * day queries use half-open [start, end) UTC bounds (AGENTS.md §23).
6913
+ */
6914
+
6915
+ declare function activityDatabasePath(memoryDir: string): string;
6916
+ declare function ensureActivityStateDir(memoryDir: string): Promise<void>;
6917
+ declare function openActivityDatabase(memoryDir: string): BetterSqlite3Database;
6918
+ /** Apply the activity schema on an already-open handle (test/in-memory use). */
6919
+ declare function applyActivitySchema(db: BetterSqlite3Database): void;
6920
+ declare class ActivityStore {
6921
+ private readonly db;
6922
+ constructor(db: BetterSqlite3Database);
6923
+ static open(memoryDir: string): ActivityStore;
6924
+ /**
6925
+ * Insert a snapshot, idempotent on (machine, captured_at_utc, content_hash):
6926
+ * the same screen content recurring at a *different* time is kept; only an
6927
+ * exact re-ingestion of the same capture dedups. Returns `inserted: false`
6928
+ * for a duplicate. The FTS row is written only on a real insert (atomically),
6929
+ * so it never drifts from the base table.
6930
+ */
6931
+ insertSnapshot(snapshot: ActivitySnapshot): {
6932
+ inserted: boolean;
6933
+ id: number;
6934
+ };
6935
+ /** Snapshots whose capture instant is in the half-open [start, end) window. */
6936
+ listSnapshotsForDay(machine: string | null, startUtcInclusive: string, endUtcExclusive: string): ActivitySnapshot[];
6937
+ getCursor(machine: string): string | null;
6938
+ setCursor(machine: string, cursor: string | null, updatedAtUtc?: string): void;
6939
+ /** Full-text search over snapshot text/app/window/url; newest first. */
6940
+ searchSnapshots(query: string, limit: number): ActivitySnapshot[];
6941
+ /** Retention: drop snapshots captured strictly before `cutoffUtc`. */
6942
+ pruneOlderThan(cutoffUtc: string): number;
6943
+ close(): void;
6944
+ }
6945
+
6946
+ /**
6947
+ * Deterministic screen-activity day-digest renderer (issue #1899).
6948
+ *
6949
+ * Renders a day's snapshots into markdown with YAML frontmatter, placed at
6950
+ * `<memoryDir>/activity/<date>.md` — outside the memory scan roots but inside
6951
+ * the QMD collection root (searchable, never auto-recalled). No LLM: the body
6952
+ * is a pure, byte-identical function of its inputs so an unchanged day skips
6953
+ * rewrite by contentHash. Day bucketing is DST-aware and half-open
6954
+ * [start, end) (AGENTS.md §23); sort keys are total with stable tiebreakers
6955
+ * (§12).
6956
+ */
6957
+
6958
+ declare const ACTIVITY_DIGEST_FORMAT_VERSION = 1;
6959
+ declare const ACTIVITY_DIR_NAME = "activity";
6960
+ declare function isValidActivityDate(date: string): boolean;
6961
+ declare function activityDigestPath(memoryDir: string, date: string): string;
6962
+ /** Half-open [start, end) UTC ISO bounds of a local day. */
6963
+ declare function activityDayWindow(date: string, timezone: string): {
6964
+ startUtc: string;
6965
+ endUtc: string;
6966
+ };
6967
+ declare function composeActivityDigestBody(date: string, timezone: string, snapshots: ActivitySnapshot[]): string;
6968
+ declare function hashActivityBody(body: string): string;
6969
+ declare function composeActivityDigestMeta(date: string, machines: string[], snapshots: ActivitySnapshot[], body: string): ActivityDayMeta;
6970
+ declare function serializeActivityDigest(meta: ActivityDayMeta, body: string): string;
6971
+ declare function parseActivityDigest(raw: string): ActivityDayDigest | null;
6972
+
6821
6973
  type LocalSessionRole = "user" | "assistant" | "tool" | "system" | "other";
6822
6974
  interface LocalSessionTurn {
6823
6975
  role: LocalSessionRole;
@@ -7134,4 +7286,4 @@ declare function forkCapsule(opts: ForkCapsuleOptions): Promise<ForkCapsuleResul
7134
7286
  */
7135
7287
  declare function readForkLineage(targetRoot: string, forkId: string): Promise<ForkLineage | null>;
7136
7288
 
7137
- export { type ArchitectureHintsResult, type AuditEntry, type BinaryAssetRecord, type BinaryAssetStatus, type BinaryLifecycleConfig, type BinaryLifecycleManifest, type BinaryStorageBackend, type BinaryStorageBackendConfig, ClaudeCodeMemoryExtensionPublisher, type CleanupResult, CodexMemoryExtensionPublisher, type CodingNamespaceOverlay, type CodingScopeDescription, type CollectLocalSessionSummariesOptions, type CompiledCorrectionRule, type CompiledRedactionRule, type ConflictEntry, type ContradictionOptions, type ContradictionPair, type ContradictionResult$1 as CurateContradictionResult, type DuplicateResult as CurateDuplicateResult, type CurateOptions, type CurateResult, type CuratedStatement, DEFAULT_GRACE_PERIOD_DAYS, DEFAULT_MAX_BINARY_SIZE_BYTES, DEFAULT_PROXIMITY_GAP_MS, DEFAULT_REDACTION_RULES, DEFAULT_SCAN_PATTERNS, DEFAULT_SOURCE_TRUST, DEFAULT_TAXONOMY, DEFAULT_WINDOW_TOLERANCE_MS, type DedupOptions, type DedupResult, DiscoveredExtension, type DocFile, type DriveChange, type DriveChangesPage, type DriveFileMetadata, type DuplicatePair, EngramAccessService, type FileChange, FilesystemBackend, type ForkCapsuleOptions, type ForkCapsuleResult, type ForkLineage, type FuseClusterResult, FusedDisagreement, FusedSegment, FusedSpeaker, FusionConversationInput, FusionDayResult, FusionOptions, GOOGLE_DRIVE_CONNECTOR_ID, GOOGLE_DRIVE_CURSOR_KIND, DEFAULT_POLL_INTERVAL_MS as GOOGLE_DRIVE_DEFAULT_POLL_INTERVAL_MS, type GenerateOptions, type GenerateResult, type GitContext, type GitInvoker, type GoogleDriveClient, type GoogleDriveClientFactory, type GoogleDriveConnectorConfig, type GoogleDriveSyncResult, HermesMemoryExtensionPublisher, type IngestionPlan, KNOWN_WEARABLE_SOURCE_IDS, type LanguageInfo, LiveConnector, LiveConnectorRegistry, LiveConnectorRegistryError, type LocalSessionAdapterInput, type LocalSessionAdapterOptions, type LocalSessionParseWarning, type LocalSessionParsedFile, type LocalSessionRole, type LocalSessionSourceAdapter, type LocalSessionSummaryCliOptions, type LocalSessionSummaryReport, type LocalSessionTurn, MemoryCategory, type MemoryEntry, type MemoryExtensionPublisher, type MergeResult, NOTION_CONNECTOR_ID, NOTION_CURSOR_KIND, NOTION_DEFAULT_POLL_INTERVAL_MS, NoneBackend, type NotionConnectorConfig, type OnboardOptions, type OnboardResult, PUBLISHERS, type PackReviewContextStructuralInput, type PipelineResult, PluginConfig, type ProjectShape, type ProvenanceEntry, type PublishContext, type PublishResult, type PublisherCapabilities, REDACTION_PLACEHOLDER, RELAY_DEMO_MISSION_ID, RELAY_DEMO_NAMESPACE, RELAY_MISSION_DEFAULT_EVENT_LIMIT, RELAY_MISSION_MAX_EVENTS, RELAY_MISSION_MAX_EVENT_LIMIT, RELAY_MISSION_MAX_FILE_BYTES, RELAY_MISSION_MAX_LINE_BYTES, RELAY_MISSION_SCHEMA_VERSION, REMNIC_CITATION_FORMAT, REMNIC_MCP_TOOL_INVENTORY, REMNIC_RECALL_DECISION_RULES, REMNIC_SEMANTIC_OVERVIEW, type RedactionConfig, type RedactionReport, type RedactionRuleConfig, type RelayAgentOutputSnapshot, type RelayAgentSnapshot, type RelayConflictSnapshot, type RelayCorrectionSnapshot, type RelayDecisionSnapshot, type RelayEvidenceRef, RelayEvidenceRefSchema, type RelayMissionAppendAccessInput, type RelayMissionAppendAccessOutput, type RelayMissionAppendResult, type RelayMissionEvent, type RelayMissionEventInput, RelayMissionEventInputSchema, RelayMissionEventSchema, type RelayMissionPayload, RelayMissionPayloadSchema, type RelayMissionReadAccessInput, type RelayMissionReadAccessOutput, type RelayMissionReadOptions, RelayMissionReadOptionsSchema, type RelayMissionSnapshot, type RelayMissionStatus, RelayMissionStore, RelayMissionStoreError, type RelayMissionStoreErrorCode, type RelayMissionStoreOptions, type RelayPropagationSnapshot, type RelayRecallSnapshot, type RelayTestSnapshot, ResolverDecision, type ReviewAction, type ReviewCandidate, type ReviewContext, type ReviewItem, type ReviewListResult, type ReviewOptions, type ReviewResult, type SessionSummaryDraft, type SessionSummaryExcerpt, type Space, type SpaceKind, type SpaceManifest, type SpacePromoteResult, type SpacePullResult, type SpacePushResult, type SpaceShareResult, type SpaceSwitchResult, SpeakerRegistry, type StatementProvenance, StorageManager, type StructuralContextDegradation, type StructuralContextErrorCode, type StructuralContextProvider, type StructuralProviderStatus, type StructuralSpawnFn, type StructuralSymbol, type SubprocessProviderOptions, type SymbolsForDiffErr, type SymbolsForDiffOk, type SymbolsForDiffResult, SyncIncrementalResult, type SyncOptions, type SyncResult, type SyncState, TRANSCRIPT_FORMAT_VERSION, Taxonomy, TokenEntry, type TrainingExportAdapter, type TrainingExportOptions, type TrainingExportRecord, type TranscriptSegmentMatch, type TreeNode, WEARABLES_DIR_NAME, WearableCleanupSettings, type WearableConnectorFactory, type WearableConnectorFactoryOptions, type WearableConnectorRegistration, WearableConversation, WearableCorrectionRule, WearableDayTranscript, WearableDayTranscriptMeta, WearableFusionConfig, WearableSourceConnector, WearableSourceSettings, type WearableSourceSyncState, type WearableSyncStateFile, type WearablesCliIo, WearablesConfig, WearablesInputError, WearablesService, appendRelayMissionEvent, applyCorrections, applyOffTheRecord, bodyIsEscaped, branchNamespaceName, buildProcedureMarkdownBody, buildProcedureRecallSection, cleanConversation, clearStructuralContextProvidersForTest, clearTrainingExportAdapters, clearWearableConnectors, clusterConversations, collapseImmediateRepeats, collectLocalSessionSummaries, compareRelayEvents, compileCorrectionRule, compileCorrectionRules, compileRedactionPatterns, compileRedactionRules, composeDayTranscriptBody, composeDayTranscriptMeta, convertMemoriesToRecords, correctionsFilePath, createBackend, createGoogleDriveConnector, createNotionConnector, createRelayMissionFixture, createSpace, createSubprocessStructuralProvider, curate, decodeTranscriptBody, defaultGoogleDriveClientFactory, defaultWearableCleanupSettings, defaultWearableFusionConfig, defaultWearableSourceSettings, defaultWearablesConfig, deleteSpace, describeCodingScope, describeErrorForOperator, describeStructuralProviderStatus, emptyManifest, emptySyncState, ensureBuiltInWearableConnectors, escapeSegmentText, findContradictions, findDuplicates, forkCapsule, fuseCluster, fuseDay, fusionInputsFromConversations, generateContextTree, generateResolverDocument, getActiveSpace, getAuditLog, getLocalSessionSourceAdapter, getManifestPath, getSpacesDir, getStructuralContextProvider, getTaxonomyDir, getTaxonomyFilePath, getTrainingExportAdapter, getWearableConnector, hashTranscriptBody, hostIdForConnector, isLowQualitySegment, isReviewPrompt, isValidTranscriptDate, listLocalSessionSourceAdapters, listReviewItems, listSpaces, listTrainingExportAdapters, listWearableConnectors, loadCorrectionsFile, loadManifest, loadSyncState, loadTaxonomy, manifestDir, manifestPath, matchesPatterns, mergeSpaces, normalizeOriginUrl, onboard, packReviewContext, packReviewContextStructural, parseDayTranscript, parseFlexibleIsoTimestamp, parseIsoOffsetTimestamp, parseIsoUtcTimestamp, parseProcedureStepsFromBody, parseTouchedFiles, parseTranscriptSegmentLine, parseWearablesConfig, performReview, probeStructuralProviderForDoctor, projectNamespaceName, promoteSpace, publisherFor, publisherForConnector, pullFromSpace, pushToSpace, rankReviewCandidates, readForkLineage, readManifest, readRelayMission, reconstructFusionInputs, redactSessionSummaryText, redactText, reduceRelayMission, registerLocalSessionSourceAdapter, registerPublisher, registerStructuralContextProvider, registerTrainingExportAdapter, registerWearableConnector, relayMissionAppendOperation, relayMissionReadOperation, relayMissionReceiptDigest, renderExtensionsBlock, renderExtensionsFooter, renderStructuralProviderStatusLine, resolveCategory, resolveCodingNamespaceOverlay, resolveGitContext, runBinaryLifecyclePipeline, runLocalSessionSummaryCliCommand, runWearablesCliCommand, saveCorrectionsFile, saveManifest, saveSyncState, saveTaxonomy, scanForBinaries, serializeDayTranscript, shareSpace, stableHash, stripFillerTokens, structuralProviderActive, switchSpace, syncChanges, syncStateFilePath, toStructuralContextDegradation, unescapeSegmentText, unescapeSpeakerLabel, updateSourceSyncState, validateGoogleDriveConfig, validateNotionConfig, validateSlug, validateTaxonomy, watchForChanges, writeManifest };
7289
+ export { ACTIVITY_DIGEST_FORMAT_VERSION, ACTIVITY_DIR_NAME, type ActivityDayDigest, type ActivityDayMeta, type ActivitySnapshot, type ActivitySnapshotPage, type ActivitySourceCheck, type ActivitySourceClient, ActivityStore, type ArchitectureHintsResult, type AuditEntry, type BinaryAssetRecord, type BinaryAssetStatus, type BinaryLifecycleConfig, type BinaryLifecycleManifest, type BinaryStorageBackend, type BinaryStorageBackendConfig, ClaudeCodeMemoryExtensionPublisher, type CleanupResult, CodexMemoryExtensionPublisher, type CodingNamespaceOverlay, type CodingScopeDescription, type CollectLocalSessionSummariesOptions, type CompiledCorrectionRule, type CompiledRedactionRule, type ConflictEntry, type ContradictionOptions, type ContradictionPair, type ContradictionResult$1 as CurateContradictionResult, type DuplicateResult as CurateDuplicateResult, type CurateOptions, type CurateResult, type CuratedStatement, DEFAULT_GRACE_PERIOD_DAYS, DEFAULT_MAX_BINARY_SIZE_BYTES, DEFAULT_PROXIMITY_GAP_MS, DEFAULT_REDACTION_RULES, DEFAULT_SCAN_PATTERNS, DEFAULT_SOURCE_TRUST, DEFAULT_TAXONOMY, DEFAULT_WINDOW_TOLERANCE_MS, type DedupOptions, type DedupResult, DiscoveredExtension, type DocFile, type DriveChange, type DriveChangesPage, type DriveFileMetadata, type DuplicatePair, EngramAccessService, type FileChange, FilesystemBackend, type ForkCapsuleOptions, type ForkCapsuleResult, type ForkLineage, type FuseClusterResult, FusedDisagreement, FusedSegment, FusedSpeaker, FusionConversationInput, FusionDayResult, FusionOptions, GOOGLE_DRIVE_CONNECTOR_ID, GOOGLE_DRIVE_CURSOR_KIND, DEFAULT_POLL_INTERVAL_MS as GOOGLE_DRIVE_DEFAULT_POLL_INTERVAL_MS, type GenerateOptions, type GenerateResult, type GitContext, type GitInvoker, type GoogleDriveClient, type GoogleDriveClientFactory, type GoogleDriveConnectorConfig, type GoogleDriveSyncResult, HermesMemoryExtensionPublisher, type IngestionPlan, KNOWN_WEARABLE_SOURCE_IDS, type LanguageInfo, LiveConnector, LiveConnectorRegistry, LiveConnectorRegistryError, type LocalSessionAdapterInput, type LocalSessionAdapterOptions, type LocalSessionParseWarning, type LocalSessionParsedFile, type LocalSessionRole, type LocalSessionSourceAdapter, type LocalSessionSummaryCliOptions, type LocalSessionSummaryReport, type LocalSessionTurn, MemoryCategory, type MemoryEntry, type MemoryExtensionPublisher, type MergeResult, NOTION_CONNECTOR_ID, NOTION_CURSOR_KIND, NOTION_DEFAULT_POLL_INTERVAL_MS, NoneBackend, type NotionConnectorConfig, type OnboardOptions, type OnboardResult, PUBLISHERS, type PackReviewContextStructuralInput, type PipelineResult, PluginConfig, type ProjectShape, type ProvenanceEntry, type PublishContext, type PublishResult, type PublisherCapabilities, REDACTION_PLACEHOLDER, RELAY_DEMO_MISSION_ID, RELAY_DEMO_NAMESPACE, RELAY_MISSION_DEFAULT_EVENT_LIMIT, RELAY_MISSION_MAX_EVENTS, RELAY_MISSION_MAX_EVENT_LIMIT, RELAY_MISSION_MAX_FILE_BYTES, RELAY_MISSION_MAX_LINE_BYTES, RELAY_MISSION_SCHEMA_VERSION, REMNIC_CITATION_FORMAT, REMNIC_MCP_TOOL_INVENTORY, REMNIC_RECALL_DECISION_RULES, REMNIC_SEMANTIC_OVERVIEW, type RedactionConfig, type RedactionReport, type RedactionRuleConfig, type RelayAgentOutputSnapshot, type RelayAgentSnapshot, type RelayConflictSnapshot, type RelayCorrectionSnapshot, type RelayDecisionSnapshot, type RelayEvidenceRef, RelayEvidenceRefSchema, type RelayMissionAppendAccessInput, type RelayMissionAppendAccessOutput, type RelayMissionAppendResult, type RelayMissionEvent, type RelayMissionEventInput, RelayMissionEventInputSchema, RelayMissionEventSchema, type RelayMissionPayload, RelayMissionPayloadSchema, type RelayMissionReadAccessInput, type RelayMissionReadAccessOutput, type RelayMissionReadOptions, RelayMissionReadOptionsSchema, type RelayMissionSnapshot, type RelayMissionStatus, RelayMissionStore, RelayMissionStoreError, type RelayMissionStoreErrorCode, type RelayMissionStoreOptions, type RelayPropagationSnapshot, type RelayRecallSnapshot, type RelayTestSnapshot, ResolverDecision, type ReviewAction, type ReviewCandidate, type ReviewContext, type ReviewItem, type ReviewListResult, type ReviewOptions, type ReviewResult, type SessionSummaryDraft, type SessionSummaryExcerpt, type Space, type SpaceKind, type SpaceManifest, type SpacePromoteResult, type SpacePullResult, type SpacePushResult, type SpaceShareResult, type SpaceSwitchResult, SpeakerRegistry, type StatementProvenance, StorageManager, type StructuralContextDegradation, type StructuralContextErrorCode, type StructuralContextProvider, type StructuralProviderStatus, type StructuralSpawnFn, type StructuralSymbol, type SubprocessProviderOptions, type SymbolsForDiffErr, type SymbolsForDiffOk, type SymbolsForDiffResult, SyncIncrementalResult, type SyncOptions, type SyncResult, type SyncState, TRANSCRIPT_FORMAT_VERSION, Taxonomy, TokenEntry, type TrainingExportAdapter, type TrainingExportOptions, type TrainingExportRecord, type TranscriptSegmentMatch, type TreeNode, WEARABLES_DIR_NAME, WearableCleanupSettings, type WearableConnectorFactory, type WearableConnectorFactoryOptions, type WearableConnectorRegistration, WearableConversation, WearableCorrectionRule, WearableDayTranscript, WearableDayTranscriptMeta, WearableFusionConfig, WearableSourceConnector, WearableSourceSettings, type WearableSourceSyncState, type WearableSyncStateFile, type WearablesCliIo, WearablesConfig, WearablesInputError, WearablesService, activityDatabasePath, activityDayWindow, activityDigestPath, appendRelayMissionEvent, applyActivitySchema, applyCorrections, applyOffTheRecord, bodyIsEscaped, branchNamespaceName, buildProcedureMarkdownBody, buildProcedureRecallSection, cleanConversation, clearStructuralContextProvidersForTest, clearTrainingExportAdapters, clearWearableConnectors, clusterConversations, collapseImmediateRepeats, collectLocalSessionSummaries, compareRelayEvents, compileCorrectionRule, compileCorrectionRules, compileRedactionPatterns, compileRedactionRules, composeActivityDigestBody, composeActivityDigestMeta, composeDayTranscriptBody, composeDayTranscriptMeta, convertMemoriesToRecords, correctionsFilePath, createBackend, createGoogleDriveConnector, createNotionConnector, createRelayMissionFixture, createSpace, createSubprocessStructuralProvider, curate, decodeTranscriptBody, defaultGoogleDriveClientFactory, defaultWearableCleanupSettings, defaultWearableFusionConfig, defaultWearableSourceSettings, defaultWearablesConfig, deleteSpace, describeCodingScope, describeErrorForOperator, describeStructuralProviderStatus, emptyManifest, emptySyncState, ensureActivityStateDir, ensureBuiltInWearableConnectors, escapeSegmentText, findContradictions, findDuplicates, forkCapsule, fuseCluster, fuseDay, fusionInputsFromConversations, generateContextTree, generateResolverDocument, getActiveSpace, getAuditLog, getLocalSessionSourceAdapter, getManifestPath, getSpacesDir, getStructuralContextProvider, getTaxonomyDir, getTaxonomyFilePath, getTrainingExportAdapter, getWearableConnector, hashActivityBody, hashTranscriptBody, hostIdForConnector, isLowQualitySegment, isReviewPrompt, isValidActivityDate, isValidTranscriptDate, listLocalSessionSourceAdapters, listReviewItems, listSpaces, listTrainingExportAdapters, listWearableConnectors, loadCorrectionsFile, loadManifest, loadSyncState, loadTaxonomy, manifestDir, manifestPath, matchesPatterns, mergeSpaces, normalizeOriginUrl, onboard, openActivityDatabase, packReviewContext, packReviewContextStructural, parseActivityDigest, parseDayTranscript, parseFlexibleIsoTimestamp, parseIsoOffsetTimestamp, parseIsoUtcTimestamp, parseProcedureStepsFromBody, parseTouchedFiles, parseTranscriptSegmentLine, parseWearablesConfig, performReview, probeStructuralProviderForDoctor, projectNamespaceName, promoteSpace, publisherFor, publisherForConnector, pullFromSpace, pushToSpace, rankReviewCandidates, readForkLineage, readManifest, readRelayMission, reconstructFusionInputs, redactSessionSummaryText, redactText, reduceRelayMission, registerLocalSessionSourceAdapter, registerPublisher, registerStructuralContextProvider, registerTrainingExportAdapter, registerWearableConnector, relayMissionAppendOperation, relayMissionReadOperation, relayMissionReceiptDigest, renderExtensionsBlock, renderExtensionsFooter, renderStructuralProviderStatusLine, resolveCategory, resolveCodingNamespaceOverlay, resolveGitContext, runBinaryLifecyclePipeline, runLocalSessionSummaryCliCommand, runWearablesCliCommand, saveCorrectionsFile, saveManifest, saveSyncState, saveTaxonomy, scanForBinaries, serializeActivityDigest, serializeDayTranscript, shareSpace, stableHash, stripFillerTokens, structuralProviderActive, switchSpace, syncChanges, syncStateFilePath, toStructuralContextDegradation, unescapeSegmentText, unescapeSpeakerLabel, updateSourceSyncState, validateGoogleDriveConfig, validateNotionConfig, validateSlug, validateTaxonomy, watchForChanges, writeManifest };
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import {
2
+ ACTIVITY_DIGEST_FORMAT_VERSION,
3
+ ACTIVITY_DIR_NAME,
4
+ ActivityStore,
2
5
  ClaudeCodeMemoryExtensionPublisher,
3
6
  CodexMemoryExtensionPublisher,
4
7
  DEFAULT_GRACE_PERIOD_DAYS,
@@ -18,11 +21,17 @@ import {
18
21
  REMNIC_RECALL_DECISION_RULES,
19
22
  REMNIC_SEMANTIC_OVERVIEW,
20
23
  WearablesService,
24
+ activityDatabasePath,
25
+ activityDayWindow,
26
+ activityDigestPath,
27
+ applyActivitySchema,
21
28
  buildProcedureRecallSection,
22
29
  clearStructuralContextProvidersForTest,
23
30
  clearWearableConnectors,
24
31
  collectLocalSessionSummaries,
25
32
  compileRedactionRules,
33
+ composeActivityDigestBody,
34
+ composeActivityDigestMeta,
26
35
  createBackend,
27
36
  createRelayMissionFixture,
28
37
  createSpace,
@@ -33,6 +42,7 @@ import {
33
42
  deleteSpace,
34
43
  describeStructuralProviderStatus,
35
44
  emptyManifest,
45
+ ensureActivityStateDir,
36
46
  ensureBuiltInWearableConnectors,
37
47
  findContradictions,
38
48
  findDuplicates,
@@ -48,8 +58,10 @@ import {
48
58
  getTaxonomyDir,
49
59
  getTaxonomyFilePath,
50
60
  getWearableConnector,
61
+ hashActivityBody,
51
62
  hostIdForConnector,
52
63
  isReviewPrompt,
64
+ isValidActivityDate,
53
65
  listLocalSessionSourceAdapters,
54
66
  listReviewItems,
55
67
  listSpaces,
@@ -62,8 +74,10 @@ import {
62
74
  matchesPatterns,
63
75
  mergeSpaces,
64
76
  onboard,
77
+ openActivityDatabase,
65
78
  packReviewContext,
66
79
  packReviewContextStructural,
80
+ parseActivityDigest,
67
81
  parseTouchedFiles,
68
82
  performReview,
69
83
  probeStructuralProviderForDoctor,
@@ -87,6 +101,7 @@ import {
87
101
  saveManifest,
88
102
  saveTaxonomy,
89
103
  scanForBinaries,
104
+ serializeActivityDigest,
90
105
  shareSpace,
91
106
  structuralProviderActive,
92
107
  switchSpace,
@@ -96,7 +111,7 @@ import {
96
111
  validateTaxonomy,
97
112
  watchForChanges,
98
113
  writeManifest
99
- } from "./chunk-7PY55HMI.js";
114
+ } from "./chunk-3VBXP5HS.js";
100
115
  import {
101
116
  WEARABLE_SOURCE_PREFIX,
102
117
  buildExtractionTurns,
@@ -1038,7 +1053,10 @@ export {
1038
1053
  ACTION_CONFIDENCE_RISK_CATEGORIES,
1039
1054
  ACTION_CONFIDENCE_RULE_KINDS,
1040
1055
  ACTIVE_STATUSES,
1056
+ ACTIVITY_DIGEST_FORMAT_VERSION,
1057
+ ACTIVITY_DIR_NAME,
1041
1058
  AccessAuditAdapter,
1059
+ ActivityStore,
1042
1060
  BRIEFING_FORMAT_ALLOWED,
1043
1061
  BootstrapEngine,
1044
1062
  CITATION_UNKNOWN,
@@ -1162,9 +1180,13 @@ export {
1162
1180
  WearablesInputError,
1163
1181
  WearablesService,
1164
1182
  WebSearchProvider,
1183
+ activityDatabasePath,
1184
+ activityDayWindow,
1185
+ activityDigestPath,
1165
1186
  appendAuditEntry,
1166
1187
  appendInteractionLog,
1167
1188
  appendRelayMissionEvent,
1189
+ applyActivitySchema,
1168
1190
  applyCorrections,
1169
1191
  applyLcmSchema,
1170
1192
  applyMemoryWorthFilter,
@@ -1238,6 +1260,8 @@ export {
1238
1260
  compileOfflineSyncExcludeGlobs,
1239
1261
  compileRedactionPatterns,
1240
1262
  compileRedactionRules,
1263
+ composeActivityDigestBody,
1264
+ composeActivityDigestMeta,
1241
1265
  composeDayTranscriptBody,
1242
1266
  composeDayTranscriptMeta,
1243
1267
  composeFusionDayMeta,
@@ -1288,6 +1312,7 @@ export {
1288
1312
  emptyManifest,
1289
1313
  emptySpeakerRegistry,
1290
1314
  emptySyncState,
1315
+ ensureActivityStateDir,
1291
1316
  ensureBuiltInWearableConnectors,
1292
1317
  ensureLcmStateDir,
1293
1318
  ensureSentinel,
@@ -1339,6 +1364,7 @@ export {
1339
1364
  hasBroadGraphIntent,
1340
1365
  hasCitation,
1341
1366
  hasEnabledLiveConnector,
1367
+ hashActivityBody,
1342
1368
  hashFusionBody,
1343
1369
  hashTranscriptBody,
1344
1370
  hostIdForConnector,
@@ -1370,6 +1396,7 @@ export {
1370
1396
  isUserBoundaryScope,
1371
1397
  isUserContextScope,
1372
1398
  isUserModelDimension,
1399
+ isValidActivityDate,
1373
1400
  isValidCachedVerdict,
1374
1401
  isValidConnectorId,
1375
1402
  isValidNamespaceValue,
@@ -1429,9 +1456,11 @@ export {
1429
1456
  observeRequestSchema,
1430
1457
  offlineSyncStateFromSnapshot,
1431
1458
  onboard,
1459
+ openActivityDatabase,
1432
1460
  openLcmDatabase,
1433
1461
  packReviewContext,
1434
1462
  packReviewContextStructural,
1463
+ parseActivityDigest,
1435
1464
  parseAllCitations,
1436
1465
  parseAnthropicMessageParts,
1437
1466
  parseBriefingFocus,
@@ -1560,6 +1589,7 @@ export {
1560
1589
  saveTokenStore,
1561
1590
  scanForBinaries,
1562
1591
  sealedWriteToLegacyArgs,
1592
+ serializeActivityDigest,
1563
1593
  serializeDayTranscript,
1564
1594
  serializeEntityFile,
1565
1595
  serializeFusionDay,
@@ -41,7 +41,7 @@ import {
41
41
  throwIfRecallAborted,
42
42
  tokenizeRecallQuery,
43
43
  utcDateKeysForLocalDay
44
- } from "./chunk-7PY55HMI.js";
44
+ } from "./chunk-3VBXP5HS.js";
45
45
  import "./chunk-5SB7SC2G.js";
46
46
  import "./chunk-3332THSR.js";
47
47
  import "./chunk-I74SUMNI.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.10.0",
3
+ "version": "9.12.0",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3045,7 +3045,7 @@
3045
3045
  "core"
3046
3046
  ],
3047
3047
  "peerDependencies": {
3048
- "@remnic/coding-graph": "^9.10.0"
3048
+ "@remnic/coding-graph": "^9.12.0"
3049
3049
  },
3050
3050
  "peerDependenciesMeta": {
3051
3051
  "@remnic/coding-graph": {
@@ -0,0 +1,180 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ activityDayWindow,
6
+ activityDigestPath,
7
+ composeActivityDigestBody,
8
+ composeActivityDigestMeta,
9
+ isValidActivityDate,
10
+ parseActivityDigest,
11
+ serializeActivityDigest,
12
+ } from "./digest.js";
13
+ import type { ActivitySnapshot } from "./types.js";
14
+
15
+ function snap(overrides: Partial<ActivitySnapshot> = {}): ActivitySnapshot {
16
+ return {
17
+ machine: "macstudio",
18
+ capturedAtUtc: "2026-03-10T14:00:00.000Z",
19
+ app: "Chrome",
20
+ windowTitle: "Roadmap",
21
+ text: "quarterly roadmap review notes",
22
+ textSource: "ax",
23
+ contentHash: "h",
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ const DAY = [
29
+ snap({ id: 1, contentHash: "a", capturedAtUtc: "2026-03-10T14:00:00.000Z", app: "Chrome", windowTitle: "Roadmap", text: "roadmap planning" }),
30
+ snap({ id: 2, contentHash: "b", capturedAtUtc: "2026-03-10T14:10:00.000Z", app: "Slack", windowTitle: "#eng", text: "deploy staging soon" }),
31
+ snap({ id: 3, contentHash: "c", capturedAtUtc: "2026-03-10T14:12:00.000Z", app: "Chrome", windowTitle: "PR 412", browserUrl: "https://github.com/x/pull/412", text: "review pull request" }),
32
+ ];
33
+
34
+ test("composeActivityDigestBody is byte-identical across two renders (deterministic)", () => {
35
+ const a = composeActivityDigestBody("2026-03-10", "America/Chicago", DAY);
36
+ const b = composeActivityDigestBody("2026-03-10", "America/Chicago", [...DAY].reverse());
37
+ assert.equal(a, b);
38
+ });
39
+
40
+ test("per-app time section is sorted with a stable tiebreaker on equal dwell", () => {
41
+ // Two apps with identical (zero) dwell — last snapshots — must order by name.
42
+ const snaps = [
43
+ snap({ id: 1, contentHash: "a", capturedAtUtc: "2026-03-10T14:00:00.000Z", app: "Zed" }),
44
+ snap({ id: 2, contentHash: "b", capturedAtUtc: "2026-03-10T14:00:00.000Z", app: "Arc" }),
45
+ ];
46
+ const body1 = composeActivityDigestBody("2026-03-10", "UTC", snaps);
47
+ const body2 = composeActivityDigestBody("2026-03-10", "UTC", [...snaps].reverse());
48
+ assert.equal(body1, body2);
49
+ // Arc sorts before Zed at equal dwell.
50
+ assert.ok(body1.indexOf("- Arc:") < body1.indexOf("- Zed:"));
51
+ });
52
+
53
+ test("contentHash is stable across renders and meta reflects inputs", () => {
54
+ const body = composeActivityDigestBody("2026-03-10", "UTC", DAY);
55
+ const meta = composeActivityDigestMeta("2026-03-10", ["laptop", "macstudio", "laptop"], DAY, body);
56
+ assert.equal(meta.kind, "activity-digest");
57
+ assert.equal(meta.snapshotCount, 3);
58
+ assert.deepEqual(meta.machines, ["laptop", "macstudio"]); // deduped + sorted
59
+ assert.equal(meta.contentHash, composeActivityDigestMeta("2026-03-10", ["macstudio"], DAY, body).contentHash);
60
+ });
61
+
62
+ test("serialize → parse round-trips meta and body", () => {
63
+ const body = composeActivityDigestBody("2026-03-10", "UTC", DAY);
64
+ const meta = composeActivityDigestMeta("2026-03-10", ["macstudio"], DAY, body);
65
+ const parsed = parseActivityDigest(serializeActivityDigest(meta, body));
66
+ assert.ok(parsed);
67
+ assert.deepEqual(parsed?.meta, meta);
68
+ assert.equal(parsed?.body, body);
69
+ });
70
+
71
+ test("parseActivityDigest returns null on malformed input", () => {
72
+ assert.equal(parseActivityDigest("no frontmatter here"), null);
73
+ assert.equal(parseActivityDigest("---\nkind: activity-digest\n(no closing fence)"), null);
74
+ assert.equal(parseActivityDigest("---\nkind: other\ndate: 2026-03-10\ncontentHash: x\n---\nbody"), null);
75
+ });
76
+
77
+ test("activityDayWindow yields half-open DST-aware bounds", () => {
78
+ const w = activityDayWindow("2026-03-10", "America/Chicago");
79
+ assert.equal(w.startUtc, "2026-03-10T05:00:00.000Z");
80
+ assert.equal(w.endUtc, "2026-03-11T05:00:00.000Z");
81
+ // Spring-forward day: CST→CDT shift reflected.
82
+ const dst = activityDayWindow("2026-03-08", "America/Chicago");
83
+ assert.equal(dst.startUtc, "2026-03-08T06:00:00.000Z");
84
+ assert.equal(dst.endUtc, "2026-03-09T05:00:00.000Z");
85
+ });
86
+
87
+ test("a snapshot at exactly local midnight lands in exactly one day", () => {
88
+ // Local midnight 2026-03-10 in America/Chicago (CST) == 06:00Z.
89
+ const midnightUtc = "2026-03-10T06:00:00.000Z";
90
+ const day10 = activityDayWindow("2026-03-10", "America/Chicago");
91
+ const day09 = activityDayWindow("2026-03-09", "America/Chicago");
92
+ // Included in the 10th (start-inclusive), excluded from the 9th (end-exclusive).
93
+ assert.ok(midnightUtc >= day10.startUtc && midnightUtc < day10.endUtc);
94
+ assert.ok(!(midnightUtc >= day09.startUtc && midnightUtc < day09.endUtc));
95
+ });
96
+
97
+ test("activityDigestPath places the file under <memoryDir>/activity/", () => {
98
+ assert.equal(activityDigestPath("/mem", "2026-03-10"), "/mem/activity/2026-03-10.md");
99
+ });
100
+
101
+ test("isValidActivityDate rejects impossible and malformed calendar days", () => {
102
+ assert.equal(isValidActivityDate("2026-03-10"), true);
103
+ assert.equal(isValidActivityDate("2026-02-30"), false); // Feb has no 30th
104
+ assert.equal(isValidActivityDate("2026-13-01"), false); // no month 13
105
+ assert.equal(isValidActivityDate("2026-00-10"), false);
106
+ assert.equal(isValidActivityDate("not-a-date"), false);
107
+ assert.equal(isValidActivityDate("2026-3-10"), false); // wrong shape
108
+ });
109
+
110
+ test("activityDigestPath rejects path-traversal / invalid dates", () => {
111
+ assert.equal(activityDigestPath("/mem", "2026-03-10"), "/mem/activity/2026-03-10.md");
112
+ assert.throws(() => activityDigestPath("/mem", "../../etc/passwd"), RangeError);
113
+ assert.throws(() => activityDigestPath("/mem", "2026-02-30"), RangeError);
114
+ });
115
+
116
+ test("activityDayWindow rejects an impossible date", () => {
117
+ assert.throws(() => activityDayWindow("2026-02-30", "UTC"), RangeError);
118
+ assert.throws(() => activityDayWindow("../evil", "UTC"), RangeError);
119
+ });
120
+
121
+ test("parseActivityDigest rejects a non-numeric snapshotCount/formatVersion", () => {
122
+ const body = composeActivityDigestBody("2026-03-10", "UTC", DAY);
123
+ const meta = composeActivityDigestMeta("2026-03-10", ["macstudio"], DAY, body);
124
+ const good = serializeActivityDigest(meta, body);
125
+ const broken = good.replace(/snapshotCount: \d+/, "snapshotCount: lots");
126
+ assert.equal(parseActivityDigest(broken), null);
127
+ });
128
+
129
+ test("dwell is scoped per capture machine — an interleaved machine can't steal it", () => {
130
+ const snaps = [
131
+ snap({ machine: "A", app: "A-app", capturedAtUtc: "2026-03-10T14:00:00.000Z", contentHash: "a1" }),
132
+ snap({ machine: "B", app: "B-app", capturedAtUtc: "2026-03-10T14:01:00.000Z", contentHash: "b1" }),
133
+ snap({ machine: "A", app: "A-app", capturedAtUtc: "2026-03-10T14:10:00.000Z", contentHash: "a2" }),
134
+ ];
135
+ const body = composeActivityDigestBody("2026-03-10", "UTC", snaps);
136
+ // A-app dwell = A@14:00 → A@14:10 = 10m (per-machine), not 1m (global next = B@14:01).
137
+ assert.ok(body.includes("- A-app: 10m"), body);
138
+ });
139
+
140
+ test("digest ordering is deterministic for same-time unsaved snapshots", () => {
141
+ const a = snap({ capturedAtUtc: "2026-03-10T14:00:00.000Z", contentHash: "h-a", app: "Alpha", text: "alpha window" });
142
+ const b = snap({ capturedAtUtc: "2026-03-10T14:00:00.000Z", contentHash: "h-b", app: "Beta", text: "beta window" });
143
+ const one = composeActivityDigestBody("2026-03-10", "UTC", [a, b]);
144
+ const two = composeActivityDigestBody("2026-03-10", "UTC", [b, a]);
145
+ assert.equal(one, two);
146
+ });
147
+
148
+ test("activityDayWindow does not backdate a skipped local midnight", () => {
149
+ // Egypt restarts DST on the last Friday of April at 00:00 (00:00 → 01:00),
150
+ // so local midnight is skipped. The day-start must land on the correct local
151
+ // day (the first existing local time), never backdated into the prior day.
152
+ const w = activityDayWindow("2026-04-24", "Africa/Cairo");
153
+ const local = new Intl.DateTimeFormat("en-CA", {
154
+ timeZone: "Africa/Cairo",
155
+ year: "numeric",
156
+ month: "2-digit",
157
+ day: "2-digit",
158
+ hour: "2-digit",
159
+ minute: "2-digit",
160
+ hour12: false,
161
+ }).format(new Date(w.startUtc));
162
+ assert.ok(local.startsWith("2026-04-24"), local);
163
+ });
164
+
165
+ test("timeline keeps a second machine's span even with the same app/window", () => {
166
+ const a = snap({ machine: "A", capturedAtUtc: "2026-03-10T14:00:00.000Z", app: "Chrome", windowTitle: "Doc", contentHash: "a1", text: "" });
167
+ const b = snap({ machine: "B", capturedAtUtc: "2026-03-10T14:00:30.000Z", app: "Chrome", windowTitle: "Doc", contentHash: "b1", text: "" });
168
+ const body = composeActivityDigestBody("2026-03-10", "UTC", [a, b]);
169
+ // machine is part of the timeline-span key, so the same app/window on a
170
+ // different machine is not coalesced away.
171
+ const timelineLines = body.split("\n").filter((line) => line.startsWith("- ["));
172
+ assert.equal(timelineLines.length, 2);
173
+ });
174
+
175
+ test("machine labels with YAML-special chars round-trip through the digest", () => {
176
+ const body = composeActivityDigestBody("2026-03-10", "UTC", DAY);
177
+ const meta = composeActivityDigestMeta("2026-03-10", ["alpha, beta", "macstudio"], DAY, body);
178
+ const parsed = parseActivityDigest(serializeActivityDigest(meta, body));
179
+ assert.deepEqual(parsed?.meta.machines, ["alpha, beta", "macstudio"]);
180
+ });