@remnic/core 9.11.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/access-cli.js +1 -1
- package/dist/access-operations.d.ts +3 -3
- package/dist/access-schema.d.ts +76 -76
- package/dist/{chunk-7PY55HMI.js → chunk-3VBXP5HS.js} +626 -38
- package/dist/chunk-3VBXP5HS.js.map +1 -0
- package/dist/index.d.ts +556 -404
- package/dist/index.js +31 -1
- package/dist/orchestrator.js +1 -1
- package/dist/schemas.d.ts +54 -54
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/package.json +2 -2
- package/src/activity/digest.test.ts +180 -0
- package/src/activity/digest.ts +429 -0
- package/src/activity/index.ts +9 -0
- package/src/activity/store.test.ts +226 -0
- package/src/activity/store.ts +352 -0
- package/src/activity/types.ts +86 -0
- package/src/index.ts +1 -1
- package/dist/chunk-7PY55HMI.js.map +0 -1
package/dist/transfer/types.d.ts
CHANGED
|
@@ -20,26 +20,26 @@ declare const ExportManifestV1Schema: z.ZodObject<{
|
|
|
20
20
|
bytes: number;
|
|
21
21
|
}>, "many">;
|
|
22
22
|
}, "strip", z.ZodTypeAny, {
|
|
23
|
-
|
|
23
|
+
format: "openclaw-engram-export";
|
|
24
24
|
createdAt: string;
|
|
25
|
+
pluginVersion: string;
|
|
25
26
|
files: {
|
|
26
27
|
path: string;
|
|
27
28
|
sha256: string;
|
|
28
29
|
bytes: number;
|
|
29
30
|
}[];
|
|
30
|
-
|
|
31
|
-
pluginVersion: string;
|
|
31
|
+
schemaVersion: 1;
|
|
32
32
|
includesTranscripts: boolean;
|
|
33
33
|
}, {
|
|
34
|
-
|
|
34
|
+
format: "openclaw-engram-export";
|
|
35
35
|
createdAt: string;
|
|
36
|
+
pluginVersion: string;
|
|
36
37
|
files: {
|
|
37
38
|
path: string;
|
|
38
39
|
sha256: string;
|
|
39
40
|
bytes: number;
|
|
40
41
|
}[];
|
|
41
|
-
|
|
42
|
-
pluginVersion: string;
|
|
42
|
+
schemaVersion: 1;
|
|
43
43
|
includesTranscripts: boolean;
|
|
44
44
|
}>;
|
|
45
45
|
type ExportManifestV1 = z.infer<typeof ExportManifestV1Schema>;
|
|
@@ -75,26 +75,26 @@ declare const ExportBundleV1Schema: z.ZodObject<{
|
|
|
75
75
|
bytes: number;
|
|
76
76
|
}>, "many">;
|
|
77
77
|
}, "strip", z.ZodTypeAny, {
|
|
78
|
-
|
|
78
|
+
format: "openclaw-engram-export";
|
|
79
79
|
createdAt: string;
|
|
80
|
+
pluginVersion: string;
|
|
80
81
|
files: {
|
|
81
82
|
path: string;
|
|
82
83
|
sha256: string;
|
|
83
84
|
bytes: number;
|
|
84
85
|
}[];
|
|
85
|
-
|
|
86
|
-
pluginVersion: string;
|
|
86
|
+
schemaVersion: 1;
|
|
87
87
|
includesTranscripts: boolean;
|
|
88
88
|
}, {
|
|
89
|
-
|
|
89
|
+
format: "openclaw-engram-export";
|
|
90
90
|
createdAt: string;
|
|
91
|
+
pluginVersion: string;
|
|
91
92
|
files: {
|
|
92
93
|
path: string;
|
|
93
94
|
sha256: string;
|
|
94
95
|
bytes: number;
|
|
95
96
|
}[];
|
|
96
|
-
|
|
97
|
-
pluginVersion: string;
|
|
97
|
+
schemaVersion: 1;
|
|
98
98
|
includesTranscripts: boolean;
|
|
99
99
|
}>;
|
|
100
100
|
records: z.ZodArray<z.ZodObject<{
|
|
@@ -109,15 +109,15 @@ declare const ExportBundleV1Schema: z.ZodObject<{
|
|
|
109
109
|
}>, "many">;
|
|
110
110
|
}, "strip", z.ZodTypeAny, {
|
|
111
111
|
manifest: {
|
|
112
|
-
|
|
112
|
+
format: "openclaw-engram-export";
|
|
113
113
|
createdAt: string;
|
|
114
|
+
pluginVersion: string;
|
|
114
115
|
files: {
|
|
115
116
|
path: string;
|
|
116
117
|
sha256: string;
|
|
117
118
|
bytes: number;
|
|
118
119
|
}[];
|
|
119
|
-
|
|
120
|
-
pluginVersion: string;
|
|
120
|
+
schemaVersion: 1;
|
|
121
121
|
includesTranscripts: boolean;
|
|
122
122
|
};
|
|
123
123
|
records: {
|
|
@@ -126,15 +126,15 @@ declare const ExportBundleV1Schema: z.ZodObject<{
|
|
|
126
126
|
}[];
|
|
127
127
|
}, {
|
|
128
128
|
manifest: {
|
|
129
|
-
|
|
129
|
+
format: "openclaw-engram-export";
|
|
130
130
|
createdAt: string;
|
|
131
|
+
pluginVersion: string;
|
|
131
132
|
files: {
|
|
132
133
|
path: string;
|
|
133
134
|
sha256: string;
|
|
134
135
|
bytes: number;
|
|
135
136
|
}[];
|
|
136
|
-
|
|
137
|
-
pluginVersion: string;
|
|
137
|
+
schemaVersion: 1;
|
|
138
138
|
includesTranscripts: boolean;
|
|
139
139
|
};
|
|
140
140
|
records: {
|
|
@@ -319,10 +319,10 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
319
319
|
identityAnchors: boolean;
|
|
320
320
|
peerProfiles: boolean;
|
|
321
321
|
};
|
|
322
|
-
|
|
322
|
+
description: string;
|
|
323
323
|
id: string;
|
|
324
|
+
schemaVersion: string;
|
|
324
325
|
version: string;
|
|
325
|
-
description: string;
|
|
326
326
|
parentCapsule: string | null;
|
|
327
327
|
parent: {
|
|
328
328
|
version: string;
|
|
@@ -340,10 +340,10 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
340
340
|
identityAnchors: boolean;
|
|
341
341
|
peerProfiles: boolean;
|
|
342
342
|
};
|
|
343
|
-
|
|
343
|
+
description: string;
|
|
344
344
|
id: string;
|
|
345
|
+
schemaVersion: string;
|
|
345
346
|
version: string;
|
|
346
|
-
description: string;
|
|
347
347
|
parentCapsule: string | null;
|
|
348
348
|
retrievalPolicy: {
|
|
349
349
|
tierWeights: Record<string, number>;
|
|
@@ -470,10 +470,10 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
470
470
|
identityAnchors: boolean;
|
|
471
471
|
peerProfiles: boolean;
|
|
472
472
|
};
|
|
473
|
-
|
|
473
|
+
description: string;
|
|
474
474
|
id: string;
|
|
475
|
+
schemaVersion: string;
|
|
475
476
|
version: string;
|
|
476
|
-
description: string;
|
|
477
477
|
parentCapsule: string | null;
|
|
478
478
|
parent: {
|
|
479
479
|
version: string;
|
|
@@ -491,10 +491,10 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
491
491
|
identityAnchors: boolean;
|
|
492
492
|
peerProfiles: boolean;
|
|
493
493
|
};
|
|
494
|
-
|
|
494
|
+
description: string;
|
|
495
495
|
id: string;
|
|
496
|
+
schemaVersion: string;
|
|
496
497
|
version: string;
|
|
497
|
-
description: string;
|
|
498
498
|
parentCapsule: string | null;
|
|
499
499
|
retrievalPolicy: {
|
|
500
500
|
tierWeights: Record<string, number>;
|
|
@@ -507,16 +507,14 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
507
507
|
} | null | undefined;
|
|
508
508
|
}>;
|
|
509
509
|
}, "strip", z.ZodTypeAny, {
|
|
510
|
-
|
|
510
|
+
format: "openclaw-engram-export";
|
|
511
511
|
createdAt: string;
|
|
512
|
+
pluginVersion: string;
|
|
512
513
|
files: {
|
|
513
514
|
path: string;
|
|
514
515
|
sha256: string;
|
|
515
516
|
bytes: number;
|
|
516
517
|
}[];
|
|
517
|
-
format: "openclaw-engram-export";
|
|
518
|
-
pluginVersion: string;
|
|
519
|
-
includesTranscripts: boolean;
|
|
520
518
|
capsule: {
|
|
521
519
|
includes: {
|
|
522
520
|
procedural: boolean;
|
|
@@ -524,10 +522,10 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
524
522
|
identityAnchors: boolean;
|
|
525
523
|
peerProfiles: boolean;
|
|
526
524
|
};
|
|
527
|
-
|
|
525
|
+
description: string;
|
|
528
526
|
id: string;
|
|
527
|
+
schemaVersion: string;
|
|
529
528
|
version: string;
|
|
530
|
-
description: string;
|
|
531
529
|
parentCapsule: string | null;
|
|
532
530
|
parent: {
|
|
533
531
|
version: string;
|
|
@@ -539,17 +537,17 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
539
537
|
directAnswerEnabled: boolean;
|
|
540
538
|
};
|
|
541
539
|
};
|
|
542
|
-
}, {
|
|
543
540
|
schemaVersion: 2;
|
|
541
|
+
includesTranscripts: boolean;
|
|
542
|
+
}, {
|
|
543
|
+
format: "openclaw-engram-export";
|
|
544
544
|
createdAt: string;
|
|
545
|
+
pluginVersion: string;
|
|
545
546
|
files: {
|
|
546
547
|
path: string;
|
|
547
548
|
sha256: string;
|
|
548
549
|
bytes: number;
|
|
549
550
|
}[];
|
|
550
|
-
format: "openclaw-engram-export";
|
|
551
|
-
pluginVersion: string;
|
|
552
|
-
includesTranscripts: boolean;
|
|
553
551
|
capsule: {
|
|
554
552
|
includes: {
|
|
555
553
|
procedural: boolean;
|
|
@@ -557,10 +555,10 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
557
555
|
identityAnchors: boolean;
|
|
558
556
|
peerProfiles: boolean;
|
|
559
557
|
};
|
|
560
|
-
|
|
558
|
+
description: string;
|
|
561
559
|
id: string;
|
|
560
|
+
schemaVersion: string;
|
|
562
561
|
version: string;
|
|
563
|
-
description: string;
|
|
564
562
|
parentCapsule: string | null;
|
|
565
563
|
retrievalPolicy: {
|
|
566
564
|
tierWeights: Record<string, number>;
|
|
@@ -572,6 +570,8 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
572
570
|
forkRoot: string;
|
|
573
571
|
} | null | undefined;
|
|
574
572
|
};
|
|
573
|
+
schemaVersion: 2;
|
|
574
|
+
includesTranscripts: boolean;
|
|
575
575
|
}>;
|
|
576
576
|
type ExportManifestV2 = z.infer<typeof ExportManifestV2Schema>;
|
|
577
577
|
declare const ExportBundleV2Schema: z.ZodObject<{
|
|
@@ -689,10 +689,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
689
689
|
identityAnchors: boolean;
|
|
690
690
|
peerProfiles: boolean;
|
|
691
691
|
};
|
|
692
|
-
|
|
692
|
+
description: string;
|
|
693
693
|
id: string;
|
|
694
|
+
schemaVersion: string;
|
|
694
695
|
version: string;
|
|
695
|
-
description: string;
|
|
696
696
|
parentCapsule: string | null;
|
|
697
697
|
parent: {
|
|
698
698
|
version: string;
|
|
@@ -710,10 +710,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
710
710
|
identityAnchors: boolean;
|
|
711
711
|
peerProfiles: boolean;
|
|
712
712
|
};
|
|
713
|
-
|
|
713
|
+
description: string;
|
|
714
714
|
id: string;
|
|
715
|
+
schemaVersion: string;
|
|
715
716
|
version: string;
|
|
716
|
-
description: string;
|
|
717
717
|
parentCapsule: string | null;
|
|
718
718
|
retrievalPolicy: {
|
|
719
719
|
tierWeights: Record<string, number>;
|
|
@@ -726,16 +726,14 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
726
726
|
} | null | undefined;
|
|
727
727
|
}>;
|
|
728
728
|
}, "strip", z.ZodTypeAny, {
|
|
729
|
-
|
|
729
|
+
format: "openclaw-engram-export";
|
|
730
730
|
createdAt: string;
|
|
731
|
+
pluginVersion: string;
|
|
731
732
|
files: {
|
|
732
733
|
path: string;
|
|
733
734
|
sha256: string;
|
|
734
735
|
bytes: number;
|
|
735
736
|
}[];
|
|
736
|
-
format: "openclaw-engram-export";
|
|
737
|
-
pluginVersion: string;
|
|
738
|
-
includesTranscripts: boolean;
|
|
739
737
|
capsule: {
|
|
740
738
|
includes: {
|
|
741
739
|
procedural: boolean;
|
|
@@ -743,10 +741,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
743
741
|
identityAnchors: boolean;
|
|
744
742
|
peerProfiles: boolean;
|
|
745
743
|
};
|
|
746
|
-
|
|
744
|
+
description: string;
|
|
747
745
|
id: string;
|
|
746
|
+
schemaVersion: string;
|
|
748
747
|
version: string;
|
|
749
|
-
description: string;
|
|
750
748
|
parentCapsule: string | null;
|
|
751
749
|
parent: {
|
|
752
750
|
version: string;
|
|
@@ -758,17 +756,17 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
758
756
|
directAnswerEnabled: boolean;
|
|
759
757
|
};
|
|
760
758
|
};
|
|
761
|
-
}, {
|
|
762
759
|
schemaVersion: 2;
|
|
760
|
+
includesTranscripts: boolean;
|
|
761
|
+
}, {
|
|
762
|
+
format: "openclaw-engram-export";
|
|
763
763
|
createdAt: string;
|
|
764
|
+
pluginVersion: string;
|
|
764
765
|
files: {
|
|
765
766
|
path: string;
|
|
766
767
|
sha256: string;
|
|
767
768
|
bytes: number;
|
|
768
769
|
}[];
|
|
769
|
-
format: "openclaw-engram-export";
|
|
770
|
-
pluginVersion: string;
|
|
771
|
-
includesTranscripts: boolean;
|
|
772
770
|
capsule: {
|
|
773
771
|
includes: {
|
|
774
772
|
procedural: boolean;
|
|
@@ -776,10 +774,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
776
774
|
identityAnchors: boolean;
|
|
777
775
|
peerProfiles: boolean;
|
|
778
776
|
};
|
|
779
|
-
|
|
777
|
+
description: string;
|
|
780
778
|
id: string;
|
|
779
|
+
schemaVersion: string;
|
|
781
780
|
version: string;
|
|
782
|
-
description: string;
|
|
783
781
|
parentCapsule: string | null;
|
|
784
782
|
retrievalPolicy: {
|
|
785
783
|
tierWeights: Record<string, number>;
|
|
@@ -791,6 +789,8 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
791
789
|
forkRoot: string;
|
|
792
790
|
} | null | undefined;
|
|
793
791
|
};
|
|
792
|
+
schemaVersion: 2;
|
|
793
|
+
includesTranscripts: boolean;
|
|
794
794
|
}>;
|
|
795
795
|
records: z.ZodArray<z.ZodObject<{
|
|
796
796
|
path: z.ZodString;
|
|
@@ -804,16 +804,14 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
804
804
|
}>, "many">;
|
|
805
805
|
}, "strip", z.ZodTypeAny, {
|
|
806
806
|
manifest: {
|
|
807
|
-
|
|
807
|
+
format: "openclaw-engram-export";
|
|
808
808
|
createdAt: string;
|
|
809
|
+
pluginVersion: string;
|
|
809
810
|
files: {
|
|
810
811
|
path: string;
|
|
811
812
|
sha256: string;
|
|
812
813
|
bytes: number;
|
|
813
814
|
}[];
|
|
814
|
-
format: "openclaw-engram-export";
|
|
815
|
-
pluginVersion: string;
|
|
816
|
-
includesTranscripts: boolean;
|
|
817
815
|
capsule: {
|
|
818
816
|
includes: {
|
|
819
817
|
procedural: boolean;
|
|
@@ -821,10 +819,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
821
819
|
identityAnchors: boolean;
|
|
822
820
|
peerProfiles: boolean;
|
|
823
821
|
};
|
|
824
|
-
|
|
822
|
+
description: string;
|
|
825
823
|
id: string;
|
|
824
|
+
schemaVersion: string;
|
|
826
825
|
version: string;
|
|
827
|
-
description: string;
|
|
828
826
|
parentCapsule: string | null;
|
|
829
827
|
parent: {
|
|
830
828
|
version: string;
|
|
@@ -836,6 +834,8 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
836
834
|
directAnswerEnabled: boolean;
|
|
837
835
|
};
|
|
838
836
|
};
|
|
837
|
+
schemaVersion: 2;
|
|
838
|
+
includesTranscripts: boolean;
|
|
839
839
|
};
|
|
840
840
|
records: {
|
|
841
841
|
content: string;
|
|
@@ -843,16 +843,14 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
843
843
|
}[];
|
|
844
844
|
}, {
|
|
845
845
|
manifest: {
|
|
846
|
-
|
|
846
|
+
format: "openclaw-engram-export";
|
|
847
847
|
createdAt: string;
|
|
848
|
+
pluginVersion: string;
|
|
848
849
|
files: {
|
|
849
850
|
path: string;
|
|
850
851
|
sha256: string;
|
|
851
852
|
bytes: number;
|
|
852
853
|
}[];
|
|
853
|
-
format: "openclaw-engram-export";
|
|
854
|
-
pluginVersion: string;
|
|
855
|
-
includesTranscripts: boolean;
|
|
856
854
|
capsule: {
|
|
857
855
|
includes: {
|
|
858
856
|
procedural: boolean;
|
|
@@ -860,10 +858,10 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
860
858
|
identityAnchors: boolean;
|
|
861
859
|
peerProfiles: boolean;
|
|
862
860
|
};
|
|
863
|
-
|
|
861
|
+
description: string;
|
|
864
862
|
id: string;
|
|
863
|
+
schemaVersion: string;
|
|
865
864
|
version: string;
|
|
866
|
-
description: string;
|
|
867
865
|
parentCapsule: string | null;
|
|
868
866
|
retrievalPolicy: {
|
|
869
867
|
tierWeights: Record<string, number>;
|
|
@@ -875,6 +873,8 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
875
873
|
forkRoot: string;
|
|
876
874
|
} | null | undefined;
|
|
877
875
|
};
|
|
876
|
+
schemaVersion: 2;
|
|
877
|
+
includesTranscripts: boolean;
|
|
878
878
|
};
|
|
879
879
|
records: {
|
|
880
880
|
content: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.
|
|
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.
|
|
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
|
+
});
|